// ======================================================================= // GUIslice library // - Calvin Hass // - https://www.impulseadventure.com/elec/guislice-gui.html // - https://github.com/ImpulseAdventure/GUIslice // // - Version 0.15.0 // ======================================================================= // // 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.c // GUIslice library // Import configuration ( which will import a sub-config depending on device type) #include "GUIslice_config.h" #include "GUIslice.h" #include "GUIslice_drv.h" #include #if defined(DBG_REDRAW) #include // For delay() #endif // DBG_REDRAW #ifdef DBG_FRAME_RATE #include // for FrameRate reporting #endif #if (GSLC_USE_FLOAT) #include #endif #if (GSLC_USE_PROGMEM) #include // For memcpy_P() #endif #include // For va_* // Version tracking #include "GUIslice_version.h" // ======================================================================== /// Global debug output function /// - The user assigns this function via gslc_InitDebug() GSLC_CB_DEBUG_OUT g_pfDebugOut = NULL; // Forward declaration for trigonometric lookup table extern uint16_t m_nLUTSinF0X16[257]; // ------------------------------------------------------------------------ // Error Strings // - These strings are generally stored in FLASH memory if possible // ------------------------------------------------------------------------ const char GSLC_PMEM ERRSTR_NULL[] = "ERROR: %z() called with NULL ptr\n"; const char GSLC_PMEM ERRSTR_PXD_NULL[] = "ERROR: %z() pXData NULL\n"; // ------------------------------------------------------------------------ // General Functions // ------------------------------------------------------------------------ char* gslc_GetVer(gslc_tsGui* pGui) { return (char*)GUISLICE_VER; } const char* gslc_GetNameDisp(gslc_tsGui* pGui) { return gslc_DrvGetNameDisp(pGui); } const char* gslc_GetNameTouch(gslc_tsGui* pGui) { return gslc_DrvGetNameTouch(pGui); } void* gslc_GetDriverDisp(gslc_tsGui* pGui) { return gslc_DrvGetDriverDisp(pGui); } void* gslc_GetDriverTouch(gslc_tsGui* pGui) { return gslc_DrvGetDriverTouch(pGui); } bool gslc_Init(gslc_tsGui* pGui,void* pvDriver,gslc_tsPage* asPage,uint8_t nMaxPage,gslc_tsFont* asFont,uint8_t nMaxFont) { unsigned nInd; bool bOk = true; // Provide indication that debug messaging is active #if !defined(INIT_MSG_DISABLE) GSLC_DEBUG_PRINT("GUIslice version [%s]:\n", gslc_GetVer(pGui)); #endif pGui->eInitStatTouch = GSLC_INITSTAT_UNDEF; // Initialize state pGui->nDisp0W = 0; pGui->nDisp0H = 0; pGui->nDispW = 0; pGui->nDispH = 0; pGui->nDispDepth = 0; #if defined(DRV_DISP_ADAGFX) || defined(DRV_DISP_ADAGFX_AS) || defined(DRV_DISP_TFT_ESPI) || defined(DRV_DISP_M5STACK) // Assign default orientation pGui->nRotation = GSLC_ROTATE; #if !defined(DRV_TOUCH_NONE) // Assign arbitrary defaults // These will be overwritten during DrvInit() pGui->nSwapXY = 0; pGui->nFlipX = 0; pGui->nFlipY = 0; pGui->nTouchCalXMin = 100; pGui->nTouchCalXMax = 1000; pGui->nTouchCalYMin = 100; pGui->nTouchCalYMax = 1000; pGui->bTouchRemapYX = false; #endif // !DRV_TOUCH_NONE #endif // Default to remapping enabled pGui->bTouchRemapEn = true; pGui->nPageMax = nMaxPage; pGui->nPageCnt = 0; pGui->asPage = asPage; for (nInd = 0; nInd < GSLC_STACK__MAX; nInd++) { pGui->apPageStack[nInd] = NULL; pGui->abPageStackActive[nInd] = true; pGui->abPageStackDoDraw[nInd] = true; } pGui->bScreenNeedRedraw = true; pGui->bScreenNeedFlip = false; gslc_InvalidateRgnReset(pGui); // Default global element characteristics pGui->nRoundRadius = 4; // Default image transparency setting // - Used when GSLC_BMP_TRANS_EN=1 // - Defined by config file GSLC_BMP_TRANS_RGB // - Can be overridden by user with SetTransparentColor() #if defined(GSLC_BMP_TRANS_RGB) pGui->sTransCol = (gslc_tsColor){ GSLC_BMP_TRANS_RGB }; #else // Provide a default if not defined in config // - The following is equivalent to GSLC_COL_MAGENTA pGui->sTransCol = (gslc_tsColor) { 0xFF, 0x00, 0xFF }; #endif // Initialize collection of fonts with user-supplied pointer pGui->asFont = asFont; pGui->nFontMax = nMaxFont; pGui->nFontCnt = 0; for (nInd=0;nInd<(pGui->nFontMax);nInd++) { gslc_ResetFont(&(pGui->asFont[nInd])); } // Initialize temporary element #if (GSLC_FEATURE_COMPOUND) gslc_ResetElem(&(pGui->sElemTmp)); #endif // Last touch event pGui->nTouchLastX = 0; pGui->nTouchLastY = 0; pGui->nTouchLastPress = 0; //pGui->pfuncXEvent = NULL; // UNUSED pGui->pfuncPinPoll = NULL; pGui->asInputMap = NULL; pGui->nInputMapMax = 0; pGui->nInputMapCnt = 0; pGui->sImgRefBkgnd = gslc_ResetImage(); // Save a link to the driver pGui->pvDriver = pvDriver; // Default to no support for partial redraw // - This may be overridden by the driver-specific init pGui->bRedrawPartialEn = false; #ifdef DBG_FRAME_RATE pGui->nFrameRateCnt = 0; pGui->nFrameRateStart = time(NULL); #endif // Initialize the display and touch drivers if (bOk) { bOk &= gslc_DrvInit(pGui); if (bOk) { #if !defined(INIT_MSG_DISABLE) GSLC_DEBUG_PRINT("- Init display handler [%s] OK\n", gslc_GetNameDisp(pGui)); #endif } else { GSLC_DEBUG_PRINT("- Init display handler [%s] FAIL\n", gslc_GetNameDisp(pGui)); } } #if defined(DRV_TOUCH_NONE) pGui->eInitStatTouch = GSLC_INITSTAT_INACTIVE; GSLC_DEBUG_PRINT("- No touch handler enabled\n", ""); #else if (bOk) { // Touch initialization is not made to be a fatal error // Instead, a flag is set that can be used to alert the // user on their display (in case the debug messaging was // not enabled) bool bTouchOk = gslc_InitTouch(pGui,GSLC_DEV_TOUCH); if (bTouchOk) { #if !defined(INIT_MSG_DISABLE) GSLC_DEBUG_PRINT("- Init touch handler [%s] OK\n", gslc_GetNameTouch(pGui)); #endif pGui->eInitStatTouch = GSLC_INITSTAT_ACTIVE; } else { GSLC_DEBUG_PRINT("- Init touch handler [%s] FAIL\n", gslc_GetNameTouch(pGui)); pGui->eInitStatTouch = GSLC_INITSTAT_FAIL; } } #endif // Initialize the entire display as being invalidated gslc_InvalidateRgnScreen(pGui); // If the display didn't initialize properly, then return // false which should mark it as a fatal error. Note that // touch handler errors are not included as fatal errors. if (!bOk) { GSLC_DEBUG_PRINT("ERROR: Init(%s) failed\n",""); } return bOk; } void gslc_SetPinPollFunc(gslc_tsGui* pGui,GSLC_CB_PIN_POLL pfunc) { pGui->pfuncPinPoll = pfunc; } void gslc_InitInputMap(gslc_tsGui* pGui,gslc_tsInputMap* asInputMap,uint8_t nInputMapMax) { #if !(GSLC_FEATURE_INPUT) return; #else pGui->asInputMap = asInputMap; pGui->nInputMapMax = nInputMapMax; pGui->nInputMapCnt = 0; #endif } void gslc_InputMapAdd(gslc_tsGui* pGui,gslc_teInputRawEvent eInputEvent,int16_t nInputVal,gslc_teAction eAction,int16_t nActionVal) { #if !(GSLC_FEATURE_INPUT) return; #else if (pGui->nInputMapCnt >= pGui->nInputMapMax) { GSLC_DEBUG2_PRINT("ERROR: InputMapAdd() too many mappings. Max=%u\n",pGui->nInputMapMax); return; } gslc_tsInputMap sInputMap; sInputMap.eEvent = eInputEvent; sInputMap.nVal = nInputVal; sInputMap.eAction = eAction; sInputMap.nActionVal = nActionVal; pGui->asInputMap[pGui->nInputMapCnt] = sInputMap; pGui->nInputMapCnt++; #endif } bool gslc_InputMapLookup(gslc_tsGui* pGui,gslc_teInputRawEvent eInputEvent,int16_t nInputVal,gslc_teAction* peAction,int16_t* pnActionVal) { #if !(GSLC_FEATURE_INPUT) return false; #else uint8_t nInputInd; uint8_t nInputMax = pGui->nInputMapCnt; bool bFound = false; gslc_tsInputMap sMapEntry; // Assign defaults *peAction = GSLC_ACTION_UNDEF; *pnActionVal = 0; // Search for (nInputInd=0;((nInputIndasInputMap[nInputInd]; if ((sMapEntry.eEvent == eInputEvent) && (sMapEntry.nVal == nInputVal)) { bFound = true; *peAction = pGui->asInputMap[nInputInd].eAction; *pnActionVal = sMapEntry.nActionVal; } } return bFound; #endif } void gslc_InitDebug(GSLC_CB_DEBUG_OUT pfunc) { g_pfDebugOut = pfunc; } // Internal enumerations for printf() parser state machine typedef enum { GSLC_S_DEBUG_PRINT_NORM, GSLC_S_DEBUG_PRINT_TOKEN, GSLC_S_DEBUG_PRINT_UINT16, GSLC_S_DEBUG_PRINT_CHAR, GSLC_S_DEBUG_PRINT_STR, GSLC_S_DEBUG_PRINT_STR_P } gslc_teDebugPrintState; // A lightweight printf() routine that calls user function for // character output (enabling redirection to Serial). Only // supports the following tokens: // - %u (16-bit unsigned int in RAM) [see NOTE] // - %d (16-bit signed int in RAM) // - %c (character) // - %s (null-terminated string in RAM) // - %z (null-terminated string in FLASH) // Format strings are expected to be in FLASH if GSLC_USE_PROGMEM enabled // NOTE: // - Due to the way variadic arguments are passed, we can't pass uint16_t on Arduino // as the parameters are promoted to "int" (ie. int16_t). Passing a value over 32767 // appears to be promoted to int32_t which involves pushing two more bytes onto // the stack, causing the remainder of the va_args() to be offset. // PRE: // - g_pfDebugOut defined void gslc_DebugPrintf(const char* pFmt, ...) { if (g_pfDebugOut) { char* pStr=NULL; unsigned nMaxDivisor; unsigned nNumRemain=0; bool bNumStart=false,bNumNeg=false; unsigned nNumDivisor=1; uint16_t nFmtInd=0; char cFmt,cOut; va_list vlist; va_start(vlist,pFmt); gslc_teDebugPrintState nState = GSLC_S_DEBUG_PRINT_NORM; // Determine maximum number digit size #if defined(__AVR__) nMaxDivisor = 10000; // ~2^16 #else nMaxDivisor = 1000000000; // ~2^32 #endif #if (GSLC_USE_PROGMEM) cFmt = pgm_read_byte(&pFmt[nFmtInd]); #else cFmt = pFmt[nFmtInd]; #endif while (cFmt != 0) { if (nState == GSLC_S_DEBUG_PRINT_NORM) { if (cFmt == '%') { nState = GSLC_S_DEBUG_PRINT_TOKEN; } else { // Normal char (g_pfDebugOut)(cFmt); } nFmtInd++; // Advance format index } else if (nState == GSLC_S_DEBUG_PRINT_TOKEN) { // Get token if (cFmt == 'd') { nState = GSLC_S_DEBUG_PRINT_UINT16; // Detect negative value and convert to unsigned value // with negation flag. This enables us to reuse the same // decoding logic. int nNumInt = va_arg(vlist,int); if (nNumInt < 0) { bNumNeg = true; nNumRemain = -nNumInt; } else { bNumNeg = false; nNumRemain = nNumInt; } bNumStart = false; nNumDivisor = nMaxDivisor; } else if (cFmt == 'u') { nState = GSLC_S_DEBUG_PRINT_UINT16; nNumRemain = va_arg(vlist,unsigned); bNumNeg = false; bNumStart = false; nNumDivisor = nMaxDivisor; } else if (cFmt == 'c') { nState = GSLC_S_DEBUG_PRINT_CHAR; cOut = (char)va_arg(vlist,unsigned); } else if (cFmt == 's') { nState = GSLC_S_DEBUG_PRINT_STR; pStr = va_arg(vlist,char*); } else if (cFmt == 'z') { nState = GSLC_S_DEBUG_PRINT_STR_P; pStr = va_arg(vlist,char*); } else { // ERROR } nFmtInd++; // Advance format index } else if (nState == GSLC_S_DEBUG_PRINT_STR) { while (*pStr != 0) { cOut = *pStr; (g_pfDebugOut)(cOut); pStr++; } nState = GSLC_S_DEBUG_PRINT_NORM; // Don't advance format string index } else if (nState == GSLC_S_DEBUG_PRINT_STR_P) { do { #if (GSLC_USE_PROGMEM) cOut = pgm_read_byte(pStr); #else cOut = *pStr; #endif if (cOut != 0) { (g_pfDebugOut)(cOut); pStr++; } } while (cOut != 0); nState = GSLC_S_DEBUG_PRINT_NORM; // Don't advance format string index } else if (nState == GSLC_S_DEBUG_PRINT_CHAR) { (g_pfDebugOut)(cOut); nState = GSLC_S_DEBUG_PRINT_NORM; } else if (nState == GSLC_S_DEBUG_PRINT_UINT16) { // Handle the negation flag if required if (bNumNeg) { cOut = '-'; (g_pfDebugOut)(cOut); bNumNeg = false; // Clear the negation flag } // We remain in this state until we have consumed all of the digits // in the original number (starting with the most significant). // Each time we process a digit, the parser doesn't advance its input. if (nNumRemain < nNumDivisor) { if (bNumStart) { cOut = '0'; (g_pfDebugOut)(cOut); } else { // We haven't started outputting a number yet // Check for special case of zero if (nNumRemain == 0) { cOut = '0'; (g_pfDebugOut)(cOut); // Now fall through to done state nNumDivisor = 1; } } } else { bNumStart = true; unsigned nValDigit = nNumRemain / nNumDivisor; cOut = nValDigit+'0'; nNumRemain -= nNumDivisor*nValDigit; (g_pfDebugOut)(cOut); } // Detect end of digit decode (ie. 1's) if (nNumDivisor == 1) { // Done nState = GSLC_S_DEBUG_PRINT_NORM; } else { // Shift the divisor by an order of magnitude nNumDivisor /= 10; } // Don't advance format string index } // Read the format string (usually the next character) #if (GSLC_USE_PROGMEM) cFmt = pgm_read_byte(&pFmt[nFmtInd]); #else cFmt = pFmt[nFmtInd]; #endif } va_end(vlist); } // g_pfDebugOut } // ------------------------------------------------------------------------ // Error strings // ------------------------------------------------------------------------ extern const char ERRSTR_NULL[]; // ------------------------------------------------------------------------ void gslc_Quit(gslc_tsGui* pGui) { // Close all elements and fonts gslc_GuiDestruct(pGui); } // Main polling loop for GUIslice void gslc_Update(gslc_tsGui* pGui) { // The touch handling logic is used by both the touchscreen // handler as well as the GPIO/pin/keyboard input controller #if !defined(DRV_TOUCH_NONE) // Check to see if we had a touch initialization error // if so, mark this on the display. if (pGui->eInitStatTouch == GSLC_INITSTAT_FAIL) { gslc_DrvDrawTxt(pGui,5,5,NULL,(char*)"ERROR: InitTouch", GSLC_TXT_DEFAULT, GSLC_COL_RED, GSLC_COL_BLACK); } // --------------------------------------------- // Touch handling // --------------------------------------------- // TODO: Move the physical button / GPIO handling (pfuncPinPoll) // outside of !defined(DRV_TOUCH_NONE) check so that we can // enable physical button handling without requiring // all of the other touch driver logic. // Consider creating a define that indicates whether the // touch coordinate handling should be compiled or not // (eg. if !DRV_TOUCH_NONE && !DRV_TOUCH_INPUT) int16_t nTouchX = 0; int16_t nTouchY = 0; uint16_t nTouchPress = 0; bool bEvent = false; gslc_teInputRawEvent eInputEvent = GSLC_INPUT_NONE; int16_t nInputVal = 0; // Handle touchscreen presses // - We clear the event queue here so that we don't fall behind // - In the time it takes to update the display, several mouse / // finger events may have occurred. If we only handle a single // motion event per display update, then we may experience very // lagging responsiveness from the controls. // - Instead, we drain the event queue before proceeding on to the // display update, giving rise to a much more responsive GUI. // The maximum number of touch events that can be handled per // main loop is defined by the GSLC_TOUCH_MAX_EVT config param. // - Note that SDL2 may synchronize the RenderPresent call to // the VSYNC, which will effectively insert a delay into the // gslc_PageRedrawGo() call below. It might be possible to // adjust this blocking behavior via SDL_RENDERER_PRESENTVSYNC. // In case we are flooded with events, limit the maximum number // that we handle in one gslc_Update() call. bool bDoneEvts = false; uint16_t nNumEvts = 0; do { bEvent = false; // -------------------------------------------------------------- // First check physical pin inputs // -------------------------------------------------------------- #if (GSLC_FEATURE_INPUT) int16_t nPinNum = -1; int16_t nPinState = 0; GSLC_CB_PIN_POLL pfuncPinPoll = pGui->pfuncPinPoll; if (pfuncPinPoll != NULL) { bEvent = (*pfuncPinPoll)(pGui,&nPinNum,&nPinState); if (bEvent) { eInputEvent = GSLC_INPUT_PIN_ASSERT; nInputVal = nPinNum; } } #endif // GSLC_FEATURE_INPUT // -------------------------------------------------------------- // If no event found yet, check touch / keyboard // -------------------------------------------------------------- if (!bEvent) { // Fetch input event, which could include touch / mouse / keyboard / pin bEvent = gslc_GetTouch(pGui, &nTouchX, &nTouchY, &nTouchPress, &eInputEvent, &nInputVal); } // -------------------------------------------------------------- // If event found, handle it // -------------------------------------------------------------- if (bEvent) { // Track and handle the input events // - Handle the events on the current page switch (eInputEvent) { case GSLC_INPUT_KEY_DOWN: gslc_TrackInput(pGui,NULL,eInputEvent,nInputVal); break; case GSLC_INPUT_KEY_UP: // NOTE: For now, only handling key-down events // TODO: gslc_TrackInput(pGui,NULL,eInputEvent,nInputVal); break; case GSLC_INPUT_PIN_ASSERT: gslc_TrackInput(pGui,NULL,eInputEvent,nInputVal); break; case GSLC_INPUT_PIN_DEASSERT: // TODO: gslc_TrackInput(pGui,NULL,eInputEvent,nInputVal); break; case GSLC_INPUT_TOUCH: // Track and handle the touch events // - Handle the events on the current page gslc_TrackTouch(pGui,NULL,nTouchX,nTouchY,nTouchPress); #ifdef DBG_TOUCH // Highlight current touch for coordinate debug gslc_tsRect rMark = gslc_ExpandRect((gslc_tsRect){(int16_t)nTouchX,(int16_t)nTouchY,1,1},1,1); gslc_DrawFrameRect(pGui,rMark,GSLC_COL_YELLOW); #endif break; case GSLC_INPUT_NONE: default: break; } nNumEvts++; } // Should we stop handling events? if ((!bEvent) || (nNumEvts >= GSLC_TOUCH_MAX_EVT)) { bDoneEvts = true; } } while (!bDoneEvts); #endif // !DRV_TOUCH_NONE // --------------------------------------------- // Issue a timer tick to all pages // - This is independent of the pages in the stack uint8_t nPageInd; gslc_tsPage* pPage = NULL; for (nPageInd=0;nPageIndnPageCnt;nPageInd++) { pPage = &pGui->asPage[nPageInd]; gslc_tsEvent sEvent = gslc_EventCreate(pGui,GSLC_EVT_TICK,0,(void*)pPage,NULL); gslc_PageEvent(pGui,sEvent); } // Perform any redraw required for current page gslc_PageRedrawGo(pGui); // Simple "frame" rate reporting // - Note that the rate is based on the number of calls to gslc_Update() // per second, which may or may not redraw the frame #ifdef DBG_FRAME_RATE pGui->nFrameRateCnt++; uint32_t nElapsed = (time(NULL) - pGui->nFrameRateStart); if (nElapsed > 0) { GSLC_DEBUG_PRINT("Update rate: %6u / sec\n",pGui->nFrameRateCnt); pGui->nFrameRateStart = time(NULL); pGui->nFrameRateCnt = 0; } #endif // Provide periodic yield // - This instruction is important for some devices such as ESP8266 #if defined(ESP8266) yield(); #endif } gslc_tsEvent gslc_EventCreate(gslc_tsGui* pGui,gslc_teEventType eType,uint8_t nSubType,void* pvScope,void* pvData) { gslc_tsEvent sEvent; sEvent.eType = eType; sEvent.nSubType = nSubType; sEvent.pvScope = pvScope; sEvent.pvData = pvData; return sEvent; } // ------------------------------------------------------------------------ // Graphics General Functions // ------------------------------------------------------------------------ bool gslc_IsInRect(int16_t nSelX,int16_t nSelY,gslc_tsRect rRect) { if ( (nSelX >= rRect.x) && (nSelX <= rRect.x+rRect.w) && (nSelY >= rRect.y) && (nSelY <= rRect.y+rRect.h) ) { return true; } else { return false; } } bool gslc_IsInWH(int16_t nSelX,int16_t nSelY,uint16_t nWidth,uint16_t nHeight) { if ( (nSelX >= 0) && (nSelX <= nWidth-1) && (nSelY >= 0) && (nSelY <= nHeight-1) ) { return true; } else { return false; } } // Ensure the coordinates are increasing from nX0->nX1 and nY0->nY1 // NOTE: UNUSED void gslc_OrderCoord(int16_t* pnX0,int16_t* pnY0,int16_t* pnX1,int16_t* pnY1) { int16_t nTmp; if ((*pnX1) < (*pnX0)) { nTmp = (*pnX0); (*pnX0) = (*pnX1); (*pnX1) = nTmp; } if ((*pnY1) < (*pnY0)) { nTmp = (*pnY0); (*pnY0) = (*pnY1); (*pnY1) = nTmp; } } bool gslc_ClipPt(gslc_tsRect* pClipRect,int16_t nX,int16_t nY) { int16_t nCX0 = pClipRect->x; int16_t nCY0 = pClipRect->y; int16_t nCX1 = pClipRect->x + pClipRect->w - 1; int16_t nCY1 = pClipRect->y + pClipRect->h - 1; if ( (nX < nCX0) || (nX > nCX1) ) { return false; } if ( (nY < nCY0) || (nY > nCY1) ) { return false; } return true; } // This routine implements a basic Cohen-Sutherland line-clipping algorithm // TODO: Optimize the code further bool gslc_ClipLine(gslc_tsRect* pClipRect,int16_t* pnX0,int16_t* pnY0,int16_t* pnX1,int16_t* pnY1) { int16_t nTmpX,nTmpY; int16_t nCXMin,nCXMax,nCYMin,nCYMax; uint8_t nRegion0,nRegion1,nRegionSel; int16_t nLX0,nLY0,nLX1,nLY1; int16_t nCX0 = pClipRect->x; int16_t nCY0 = pClipRect->y; int16_t nCX1 = pClipRect->x + pClipRect->w - 1; int16_t nCY1 = pClipRect->y + pClipRect->h - 1; if (nCX0 > nCX1) { nCXMin = nCX1; nCXMax = nCX0; } else { nCXMin = nCX0; nCXMax = nCX1; } if (nCY0 > nCY1) { nCYMin = nCY1; nCYMax = nCY0; } else { nCYMin = nCY0; nCYMax = nCY1; } nTmpX = 0; nTmpY = 0; while (1) { nLX0 = *pnX0; nLY0 = *pnY0; nLX1 = *pnX1; nLY1 = *pnY1; // Step 1: Assign a region code to each endpoint nRegion0 = 0; nRegion1 = 0; if (nLX0 < nCX0) { nRegion0 |= 1; } else if (nLX0 > nCX1) { nRegion0 |= 2; } if (nLY0 < nCY0) { nRegion0 |= 4; } else if (nLY0 > nCY1) { nRegion0 |= 8; } if (nLX1 < nCX0) { nRegion1 |= 1; } else if (nLX1 > nCX1) { nRegion1 |= 2; } if (nLY1 < nCY0) { nRegion1 |= 4; } else if (nLY1 > nCY1) { nRegion1 |= 8; } // Step 2: Check for complete inclusion if ((nRegion0 == 0) && (nRegion1 == 0)) { return true; } // Step 3: Check for complete exclusion if ((nRegion0 & nRegion1) != 0) { return false; } // Step 4: Clipping nRegionSel = nRegion0 ? nRegion0 : nRegion1; if (nRegionSel & 8) { nTmpX = nLX0 + (nLX1 - nLX0) * (nCYMax - nLY0) / (nLY1 - nLY0); nTmpY = nCYMax; } else if (nRegionSel & 4) { nTmpX = nLX0 + (nLX1 - nLX0) * (nCYMin - nLY0) / (nLY1 - nLY0); nTmpY = nCYMin; } else if (nRegionSel & 2) { nTmpY = nLY0 + (nLY1 - nLY0) * (nCXMax - nLX0) / (nLX1 - nLX0); nTmpX = nCXMax; } else if (nRegionSel & 1) { nTmpY = nLY0 + (nLY1 - nLY0) * (nCXMin - nLX0) / (nLX1 - nLX0); nTmpX = nCXMin; } // Update endpoint if (nRegionSel == nRegion0) { *pnX0 = nTmpX; *pnY0 = nTmpY; } else { *pnX1 = nTmpX; *pnY1 = nTmpY; } } // while(1) return true; } bool gslc_ClipRect(gslc_tsRect* pClipRect,gslc_tsRect* pRect) { int16_t nCX0 = pClipRect->x; int16_t nCY0 = pClipRect->y; int16_t nCX1 = pClipRect->x + pClipRect->w - 1; int16_t nCY1 = pClipRect->y + pClipRect->h - 1; int16_t nRX0 = pRect->x; int16_t nRY0 = pRect->y; int16_t nRX1 = pRect->x + pRect->w - 1; int16_t nRY1 = pRect->y + pRect->h - 1; // Check for completely out of clip view if ( (nRX1 < nCX0) || (nRX0 > nCX1) ) { return false; } if ( (nRY1 < nCY0) || (nRY0 > nCY1) ) { return false; } // Reduce rect as required to fit view nRX0 = (nRX0nCX1)? nCX1 : nRX1; nRY1 = (nRY1>nCY1)? nCY1 : nRY1; pRect->x = nRX0; pRect->y = nRY0; pRect->w = nRX1-nRX0+1; pRect->h = nRY1-nRY0+1; return true; } gslc_tsImgRef gslc_ResetImage() { gslc_tsImgRef sImgRef; sImgRef.eImgFlags = GSLC_IMGREF_NONE; sImgRef.pFname = NULL; sImgRef.pImgBuf = NULL; sImgRef.pvImgRaw = NULL; return sImgRef; } gslc_tsImgRef gslc_GetImageFromFile(const char* pFname,gslc_teImgRefFlags eFmt) { gslc_tsImgRef sImgRef; sImgRef.eImgFlags = GSLC_IMGREF_SRC_FILE | (GSLC_IMGREF_FMT & eFmt); sImgRef.pFname = pFname; sImgRef.pImgBuf = NULL; sImgRef.pvImgRaw = NULL; return sImgRef; } gslc_tsImgRef gslc_GetImageFromSD(const char* pFname,gslc_teImgRefFlags eFmt) { gslc_tsImgRef sImgRef; #if (GSLC_SD_EN) sImgRef.eImgFlags = GSLC_IMGREF_SRC_SD | (GSLC_IMGREF_FMT & eFmt); sImgRef.pFname = pFname; sImgRef.pImgBuf = NULL; sImgRef.pvImgRaw = NULL; #else // TODO: Change message to also handle non-Arduino output GSLC_DEBUG2_PRINT("ERROR: GetImageFromSD(%s) not supported as Config:GSLC_SD_EN=0\n",""); sImgRef.eImgFlags = GSLC_IMGREF_NONE; #endif return sImgRef; } gslc_tsImgRef gslc_GetImageFromRam(unsigned char* pImgBuf,gslc_teImgRefFlags eFmt) { gslc_tsImgRef sImgRef; sImgRef.eImgFlags = GSLC_IMGREF_SRC_RAM | (GSLC_IMGREF_FMT & eFmt); sImgRef.pFname = NULL; sImgRef.pImgBuf = pImgBuf; sImgRef.pvImgRaw = NULL; return sImgRef; } gslc_tsImgRef gslc_GetImageFromProg(const unsigned char* pImgBuf,gslc_teImgRefFlags eFmt) { gslc_tsImgRef sImgRef; #if (GSLC_USE_PROGMEM) sImgRef.eImgFlags = GSLC_IMGREF_SRC_PROG | (GSLC_IMGREF_FMT & eFmt); #else // Fallback to RAM if PROGMEM not implemented sImgRef.eImgFlags = GSLC_IMGREF_SRC_RAM | (GSLC_IMGREF_FMT & eFmt); #endif sImgRef.pFname = NULL; sImgRef.pImgBuf = pImgBuf; sImgRef.pvImgRaw = NULL; return sImgRef; } // Sine function with optional lookup table // - Note that the n64Ang range is limited by 16-bit integers // to an effective degree range of -511 to +511 degrees, // defined by the max integer range: 32767/64. int16_t gslc_sinFX(int16_t n64Ang) { int16_t nRetValS; #if (GSLC_USE_FLOAT) // Use floating-point math library function // Calculate angle in radians float fAngRad = n64Ang*GSLC_2PI/(360.0*64.0); // Perform floating point calc float fSin = sin(fAngRad); // Return as fixed point result nRetValS = fSin * 32767.0; return nRetValS; #else // TODO: Clean up excess integer typecasting // Use lookup tables bool bNegate = false; // Support multiple waveform periods if (n64Ang >= 360*64) { n64Ang = n64Ang - (360*64); } else if (n64Ang <= -360*64) { n64Ang = n64Ang + (360*64); } // Handle negative range if (n64Ang < 0) { n64Ang = -n64Ang; bNegate = !bNegate; } // Handle 3rd and 4th phase if (n64Ang >= 180*64) { n64Ang -= 180*64; bNegate = !bNegate; } // Handle 2nd phase if (n64Ang >= 90*64) { n64Ang = 180*64 - n64Ang; } // n64Ang is quarter-phase range [0 .. 90*64] // suitable for lookup table indexing uint16_t nLutInd = ((uint32_t)n64Ang * 256)/(90*64); uint16_t nLutVal = m_nLUTSinF0X16[nLutInd]; // Leave MSB for the signed bit nLutVal /= 2; if (bNegate) { nRetValS = -nLutVal; } else { nRetValS = nLutVal; } return nRetValS; #endif } // Cosine function with optional lookup table // - Note that the n64Ang range is limited by 16-bit integers // to an effective degree range of -511 to +511 degrees, // defined by the max integer range: 32767/64. int16_t gslc_cosFX(int16_t n64Ang) { #if (GSLC_USE_FLOAT) int16_t nRetValS; // Use floating-point math library function // Calculate angle in radians float fAngRad = n64Ang * GSLC_2PI / (360.0*64.0); // Perform floating point calc float fCos = cos(fAngRad); // Return as fixed point result nRetValS = fCos * 32767.0; return nRetValS; #else // Use lookup tables // Cosine function is equivalent to Sine shifted by 90 degrees // - To avoid int16_t overflow, we trap range beyond 360 degrees // and shift the angle by one rotation (360 degrees). // - This is necessary because otherwise the +90*64 would reduce the // effective range of cosFX() to +421 degrees. if (n64Ang >= 360 * 64) { return gslc_sinFX((n64Ang - 360*64) + 90*64); } else { return gslc_sinFX(n64Ang+90*64); } #endif } // Convert from polar to cartesian void gslc_PolarToXY(uint16_t nRad,int16_t n64Ang,int16_t* nDX,int16_t* nDY) { int32_t nTmp; // TODO: Clean up excess integer typecasting nTmp = (int32_t)nRad * gslc_sinFX(n64Ang); *nDX = nTmp / 32767; nTmp = (int32_t)nRad * -gslc_cosFX(n64Ang); *nDY = nTmp / 32767; } // Call with nMidAmt=500 to create simple linear blend between two colors gslc_tsColor gslc_ColorBlend2(gslc_tsColor colStart,gslc_tsColor colEnd,uint16_t nMidAmt,uint16_t nBlendAmt) { gslc_tsColor colMid; colMid.r = (colEnd.r+colStart.r)/2; colMid.g = (colEnd.g+colStart.g)/2; colMid.b = (colEnd.b+colStart.b)/2; return gslc_ColorBlend3(colStart,colMid,colEnd,nMidAmt,nBlendAmt); } gslc_tsColor gslc_ColorBlend3(gslc_tsColor colStart,gslc_tsColor colMid,gslc_tsColor colEnd,uint16_t nMidAmt,uint16_t nBlendAmt) { gslc_tsColor colNew; nMidAmt = (nMidAmt >1000)?1000:nMidAmt; nBlendAmt = (nBlendAmt>1000)?1000:nBlendAmt; uint16_t nRngLow = nMidAmt; uint16_t nRngHigh = 1000-nMidAmt; int32_t nSubBlendAmt; if (nBlendAmt >= nMidAmt) { nSubBlendAmt = (int32_t)(nBlendAmt - nMidAmt)*1000/nRngHigh; colNew.r = nSubBlendAmt*(colEnd.r - colMid.r)/1000 + colMid.r; colNew.g = nSubBlendAmt*(colEnd.g - colMid.g)/1000 + colMid.g; colNew.b = nSubBlendAmt*(colEnd.b - colMid.b)/1000 + colMid.b; } else { nSubBlendAmt = (int32_t)(nBlendAmt - 0)*1000/nRngLow; colNew.r = nSubBlendAmt*(colMid.r - colStart.r)/1000 + colStart.r; colNew.g = nSubBlendAmt*(colMid.g - colStart.g)/1000 + colStart.g; colNew.b = nSubBlendAmt*(colMid.b - colStart.b)/1000 + colStart.b; } return colNew; } bool gslc_ColorEqual(gslc_tsColor a,gslc_tsColor b) { return a.r == b.r && a.g == b.g && a.b == b.b; } // ------------------------------------------------------------------------ // Graphics Primitive Functions // ------------------------------------------------------------------------ void gslc_DrawSetPixel(gslc_tsGui* pGui,int16_t nX,int16_t nY,gslc_tsColor nCol) { #if (DRV_HAS_DRAW_POINT) // Call optimized driver point drawing gslc_DrvDrawPoint(pGui,nX,nY,nCol); #else GSLC_DEBUG2_PRINT("ERROR: Mandatory DrvDrawPoint() is not defined in driver\n"); #endif gslc_PageFlipSet(pGui,true); } // Draw an arbitrary line using Bresenham's algorithm // - Algorithm reference: https://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#C void gslc_DrawLine(gslc_tsGui* pGui,int16_t nX0,int16_t nY0,int16_t nX1,int16_t nY1,gslc_tsColor nCol) { #if (DRV_HAS_DRAW_LINE) // Call optimized driver line drawing gslc_DrvDrawLine(pGui,nX0,nY0,nX1,nY1,nCol); #else // Perform Bresenham's line algorithm int16_t nDX = abs(nX1-nX0); int16_t nDY = abs(nY1-nY0); int16_t nSX = (nX0 < nX1)? 1 : -1; int16_t nSY = (nY0 < nY1)? 1 : -1; int16_t nErr = ( (nDX>nDY)? nDX : -nDY )/2; int16_t nE2; // Check for degenerate cases // TODO: Need to test these optimizations bool bDone = false; if (nDX == 0) { if (nDY == 0) { return; } else if (nY1-nY0 >= 0) { gslc_DrawLineV(pGui,nX0,nY0,nDY+1,nCol); bDone = true; } else { gslc_DrawLineV(pGui,nX1,nY1,nDY+1,nCol); bDone = true; } } else if (nDY == 0) { if (nX1-nX0 >= 0) { gslc_DrawLineH(pGui,nX0,nY0,nDX+1,nCol); bDone = true; } else { gslc_DrawLineH(pGui,nX1,nY1,nDX+1,nCol); bDone = true; } } if (!bDone) { for (;;) { // Set the pixel gslc_DrvDrawPoint(pGui,nX0,nY0,nCol); // Calculate next coordinates if ( (nX0 == nX1) && (nY0 == nY1) ) break; nE2 = nErr; if (nE2 > -nDX) { nErr -= nDY; nX0 += nSX; } if (nE2 < nDY) { nErr += nDX; nY0 += nSY; } } } gslc_PageFlipSet(pGui,true); #endif } void gslc_DrawLineH(gslc_tsGui* pGui,int16_t nX, int16_t nY, uint16_t nW,gslc_tsColor nCol) { uint16_t nOffset; for (nOffset=0;nOffsetw == 0) || (pRect->h == 0)) { // No source region defined, simply copy add region *pRect = rAddRect; return; } // Source region valid, so increase dimensions // Calculate the rect boundary coordinates nSrcX0 = pRect->x; nSrcY0 = pRect->y; nSrcX1 = pRect->x + pRect->w - 1; nSrcY1 = pRect->y + pRect->h - 1; nAddX0 = rAddRect.x; nAddY0 = rAddRect.y; nAddX1 = rAddRect.x + rAddRect.w - 1; nAddY1 = rAddRect.y + rAddRect.h - 1; // Find the new maximal dimensions nSrcX0 = (nAddX0 < nSrcX0) ? nAddX0 : nSrcX0; nSrcY0 = (nAddY0 < nSrcY0) ? nAddY0 : nSrcY0; nSrcX1 = (nAddX1 > nSrcX1) ? nAddX1 : nSrcX1; nSrcY1 = (nAddY1 > nSrcY1) ? nAddY1 : nSrcY1; // Update the original rect region pRect->x = nSrcX0; pRect->y = nSrcY0; pRect->w = nSrcX1 - nSrcX0 + 1; pRect->h = nSrcY1 - nSrcY0 + 1; } void gslc_InvalidateRgnReset(gslc_tsGui* pGui) { #if defined(DBG_REDRAW) GSLC_DEBUG_PRINT("DBG: InvRgnReset\n", ""); #endif pGui->bInvalidateEn = false; pGui->rInvalidateRect = (gslc_tsRect) { 0, 0, 1, 1 }; } void gslc_InvalidateRgnScreen(gslc_tsGui* pGui) { #if defined(DBG_REDRAW) GSLC_DEBUG_PRINT("DBG: InvRgnScreen\n", ""); #endif pGui->bInvalidateEn = true; pGui->rInvalidateRect = (gslc_tsRect) { 0, 0, pGui->nDispW, pGui->nDispH }; } void gslc_InvalidateRgnPage(gslc_tsGui* pGui, gslc_tsPage* pPage) { if (pPage == NULL) { return; } #if defined(DBG_REDRAW) GSLC_DEBUG_PRINT("DBG: InvRgnPage: Page=%d (%d,%d)-(%d,%d)\n", pPage->nPageId, pPage->rBounds.x, pPage->rBounds.y, pPage->rBounds.x + pPage->rBounds.w - 1, pPage->rBounds.y + pPage->rBounds.h - 1); //xxx #endif // DBG_REDRAW gslc_InvalidateRgnAdd(pGui, pPage->rBounds); pGui->bInvalidateEn = true; } void gslc_InvalidateRgnAdd(gslc_tsGui* pGui, gslc_tsRect rAddRect) { if (pGui->bInvalidateEn) { gslc_UnionRect(&(pGui->rInvalidateRect), rAddRect); } else { pGui->bInvalidateEn = true; pGui->rInvalidateRect = rAddRect; } } // Draw a circle using midpoint circle algorithm // - Algorithm reference: https://en.wikipedia.org/wiki/Midpoint_circle_algorithm void gslc_DrawFrameCircle(gslc_tsGui* pGui,int16_t nMidX,int16_t nMidY, uint16_t nRadius,gslc_tsColor nCol) { #if (DRV_HAS_DRAW_CIRCLE_FRAME) // Call optimized driver implementation gslc_DrvDrawFrameCircle(pGui,nMidX,nMidY,nRadius,nCol); #else // Emulate circle with point drawing int16_t nX = nRadius; int16_t nY = 0; int16_t nErr = 0; #if (DRV_HAS_DRAW_POINTS) gslc_tsPt asPt[8]; while (nX >= nY) { asPt[0] = (gslc_tsPt){nMidX + nX, nMidY + nY}; asPt[1] = (gslc_tsPt){nMidX + nY, nMidY + nX}; asPt[2] = (gslc_tsPt){nMidX - nY, nMidY + nX}; asPt[3] = (gslc_tsPt){nMidX - nX, nMidY + nY}; asPt[4] = (gslc_tsPt){nMidX - nX, nMidY - nY}; asPt[5] = (gslc_tsPt){nMidX - nY, nMidY - nX}; asPt[6] = (gslc_tsPt){nMidX + nY, nMidY - nX}; asPt[7] = (gslc_tsPt){nMidX + nX, nMidY - nY}; gslc_DrvDrawPoints(pGui,asPt,8,nCol); nY += 1; nErr += 1 + 2*nY; if (2*(nErr-nX) + 1 > 0) { nX -= 1; nErr += 1 - 2*nX; } } // while #elif (DRV_HAS_DRAW_POINT) while (nX >= nY) { gslc_DrvDrawPoint(pGui,nMidX + nX, nMidY + nY,nCol); gslc_DrvDrawPoint(pGui,nMidX + nY, nMidY + nX,nCol); gslc_DrvDrawPoint(pGui,nMidX - nY, nMidY + nX,nCol); gslc_DrvDrawPoint(pGui,nMidX - nX, nMidY + nY,nCol); gslc_DrvDrawPoint(pGui,nMidX - nX, nMidY - nY,nCol); gslc_DrvDrawPoint(pGui,nMidX - nY, nMidY - nX,nCol); gslc_DrvDrawPoint(pGui,nMidX + nY, nMidY - nX,nCol); gslc_DrvDrawPoint(pGui,nMidX + nX, nMidY - nY,nCol); nY += 1; nErr += 1 + 2*nY; if (2*(nErr-nX) + 1 > 0) { nX -= 1; nErr += 1 - 2*nX; } } // while #else // ERROR #endif #endif gslc_PageFlipSet(pGui,true); } // Draw a filled circle using midpoint circle algorithm // - Algorithm reference: https://en.wikipedia.org/wiki/Midpoint_circle_algorithm // - Adapted for fill by connecting points by lines // - No separate frame color is performed void gslc_DrawFillCircle(gslc_tsGui* pGui,int16_t nMidX,int16_t nMidY, uint16_t nRadius,gslc_tsColor nCol) { #if (DRV_HAS_DRAW_CIRCLE_FILL) // Call optimized driver implementation gslc_DrvDrawFillCircle(pGui,nMidX,nMidY,nRadius,nCol); #else // Emulate circle with line drawing int16_t nX = nRadius; // a int16_t nY = 0; // b int16_t nErr = 0; while (nX >= nY) { // Connect pairs of the reflected points around the circumference //gslc_DrvDrawPoint(pGui,nMidX - nY, nMidY + nX,nCol); // (-b,+a) //gslc_DrvDrawPoint(pGui,nMidX + nY, nMidY + nX,nCol); // (+b,+a) gslc_DrawLine(pGui,nMidX-nY,nMidY+nX,nMidX+nY,nMidY+nX,nCol); //gslc_DrvDrawPoint(pGui,nMidX - nX, nMidY + nY,nCol); // (-a,+b) //gslc_DrvDrawPoint(pGui,nMidX + nX, nMidY + nY,nCol); // (+a,+b) gslc_DrawLine(pGui,nMidX-nX,nMidY+nY,nMidX+nX,nMidY+nY,nCol); //gslc_DrvDrawPoint(pGui,nMidX - nX, nMidY - nY,nCol); // (-a,-b) //gslc_DrvDrawPoint(pGui,nMidX + nX, nMidY - nY,nCol); // (+a,-b) gslc_DrawLine(pGui,nMidX-nX,nMidY-nY,nMidX+nX,nMidY-nY,nCol); //gslc_DrvDrawPoint(pGui,nMidX - nY, nMidY - nX,nCol); // (-b,-a) //gslc_DrvDrawPoint(pGui,nMidX + nY, nMidY - nX,nCol); // (+b,-a) gslc_DrawLine(pGui,nMidX-nY,nMidY-nX,nMidX+nY,nMidY-nX,nCol); nY += 1; nErr += 1 + 2*nY; if (2*(nErr-nX) + 1 > 0) { nX -= 1; nErr += 1 - 2*nX; } } // while #endif gslc_PageFlipSet(pGui,true); } // Draw a triangle void gslc_DrawFrameTriangle(gslc_tsGui* pGui,int16_t nX0,int16_t nY0, int16_t nX1,int16_t nY1,int16_t nX2,int16_t nY2,gslc_tsColor nCol) { #if (DRV_HAS_DRAW_TRI_FRAME) // Call optimized driver implementation gslc_DrvDrawFrameTriangle(pGui,nX0,nY0,nX1,nY1,nX2,nY2,nCol); #else // Draw triangle with three lines gslc_DrawLine(pGui,nX0,nY0,nX1,nY1,nCol); gslc_DrawLine(pGui,nX1,nY1,nX2,nY2,nCol); gslc_DrawLine(pGui,nX2,nY2,nX0,nY0,nCol); #endif gslc_PageFlipSet(pGui,true); } void gslc_SwapCoords(int16_t* pnXa,int16_t* pnYa,int16_t* pnXb,int16_t* pnYb) { int16_t nSwapX,nSwapY; nSwapX = *pnXa; nSwapY = *pnYa; *pnXa = *pnXb; *pnYa = *pnYb; *pnXb = nSwapX; *pnYb = nSwapY; } // Draw a filled triangle void gslc_DrawFillTriangle(gslc_tsGui* pGui,int16_t nX0,int16_t nY0, int16_t nX1,int16_t nY1,int16_t nX2,int16_t nY2,gslc_tsColor nCol) { #if (DRV_HAS_DRAW_TRI_FILL) // Call optimized driver implementation gslc_DrvDrawFillTriangle(pGui,nX0,nY0,nX1,nY1,nX2,nY2,nCol); #else // Emulate triangle fill // Algorithm: // - An arbitrary triangle is cut into two portions: // 1) a flat bottom triangle // 2) a flat top triangle // - Sort the vertices in descending vertical position. // This serves two purposes: // 1) ensures that the division between the flat bottom // and flat top triangles occurs at Y=Y1 // 2) ensure that we avoid division-by-zero in the for loops // - Walk each scan line and determine the intersection // between triangle side A & B (flat bottom triangle) // and then C and B (flat top triangle) using line slopes. // Sort vertices // - Want nY0 >= nY1 >= nY2 if (nY2>nY1) { gslc_SwapCoords(&nX2,&nY2,&nX1,&nY1); } if (nY1>nY0) { gslc_SwapCoords(&nX0,&nY0,&nX1,&nY1); } if (nY2>nY1) { gslc_SwapCoords(&nX2,&nY2,&nX1,&nY1); } // TODO: It is more efficient to calculate row endpoints // using incremental additions instead of multiplies/divides int16_t nXa,nXb,nXc,nYos; int16_t nX01,nX20,nY01,nY20,nX21,nY21; nX01 = nX0-nX1; nY01 = nY0-nY1; nX20 = nX2-nX0; nY20 = nY2-nY0; nX21 = nX2-nX1; nY21 = nY2-nY1; // Flat bottom scenario // NOTE: Due to vertex sorting and loop range, it shouldn't // be possible to enter loop when nY0 == nY1 or nY2 for (nYos=0;nYos=0)?abs(nY01):-abs(nY01); nXa /= 2*nY01; nXb = 2*(nYos-nY01)*nX20; nXb += (nXb>=0)?abs(nY20):-abs(nY20); nXb /= 2*nY20; // Draw horizontal line between endpoints gslc_DrawLine(pGui,nX1+nXa,nY1+nYos,nX0+nXb,nY1+nYos,nCol); } // Flat top scenario // NOTE: Due to vertex sorting and loop range, it shouldn't // be possible to enter loop when nY2 == nY0 or nY1 for (nYos=nY21;nYos<0;nYos++) { // Determine row endpoints (no rounding) //nXc = (nYos )*(nX2-nX1)/(nY2-nY1); //nXb = (nYos-(nY0-nY1))*(nX2-nX0)/(nY2-nY0); // Determine row endpoints (using rounding) nXc = 2*(nYos)*nX21; nXc += (nXc>=0)?abs(nY21):-abs(nY21); nXc /= 2*nY21; nXb = 2*(nYos-nY01)*nX20; nXb += (nXb>=0)?abs(nY20):-abs(nY20); nXb /= 2*nY20; // Draw horizontal line between endpoints gslc_DrawLine(pGui,nX1+nXc,nY1+nYos,nX0+nXb,nY1+nYos,nCol); } #endif // DRV_HAS_DRAW_TRI_FILL gslc_PageFlipSet(pGui,true); } void gslc_DrawFrameQuad(gslc_tsGui* pGui,gslc_tsPt* psPt,gslc_tsColor nCol) { int16_t nX0,nY0,nX1,nY1; nX0 = psPt[0].x; nY0 = psPt[0].y; nX1 = psPt[1].x; nY1 = psPt[1].y; gslc_DrawLine(pGui,nX0,nY0,nX1,nY1,nCol); nX0 = psPt[1].x; nY0 = psPt[1].y; nX1 = psPt[2].x; nY1 = psPt[2].y; gslc_DrawLine(pGui,nX0,nY0,nX1,nY1,nCol); nX0 = psPt[2].x; nY0 = psPt[2].y; nX1 = psPt[3].x; nY1 = psPt[3].y; gslc_DrawLine(pGui,nX0,nY0,nX1,nY1,nCol); nX0 = psPt[3].x; nY0 = psPt[3].y; nX1 = psPt[0].x; nY1 = psPt[0].y; gslc_DrawLine(pGui,nX0,nY0,nX1,nY1,nCol); } // Filling a quadrilateral is done by breaking it down into // two filled triangles sharing one side. We have to be careful // about the triangle fill routine (ie. using rounding) so that // we can avoid leaving a thin seam between the two triangles. void gslc_DrawFillQuad(gslc_tsGui* pGui,gslc_tsPt* psPt,gslc_tsColor nCol) { int16_t nX0,nY0,nX1,nY1,nX2,nY2; // Break down quadrilateral into two triangles nX0 = psPt[0].x; nY0 = psPt[0].y; nX1 = psPt[1].x; nY1 = psPt[1].y; nX2 = psPt[2].x; nY2 = psPt[2].y; gslc_DrawFillTriangle(pGui,nX0,nY0,nX1,nY1,nX2,nY2,nCol); nX0 = psPt[2].x; nY0 = psPt[2].y; nX1 = psPt[0].x; nY1 = psPt[0].y; nX2 = psPt[3].x; nY2 = psPt[3].y; gslc_DrawFillTriangle(pGui,nX0,nY0,nX1,nY1,nX2,nY2,nCol); } void gslc_DrawFillSectorBase(gslc_tsGui* pGui, int16_t nQuality, int16_t nMidX, int16_t nMidY, int16_t nRad1, int16_t nRad2, gslc_tsColor cArcStart, gslc_tsColor cArcEnd,bool bGradient, int16_t nAngGradStart, int16_t nAngGradRange,int16_t nAngSecStart,int16_t nAngSecEnd) { gslc_tsPt anPts[4]; // Calculate degrees per step (based on quality setting) int16_t nStepAng = 360 / nQuality; int16_t nStep64 = 64 * nStepAng; int16_t nAng64; int16_t nX, nY; int16_t nSegStart, nSegEnd; gslc_tsColor colSeg; nSegStart = nAngSecStart * (int32_t)nQuality / 360; nSegEnd = nAngSecEnd * (int32_t)nQuality / 360; int16_t nSegGradStart, nSegGradRange; nSegGradStart = nAngGradStart * (int32_t)nQuality / 360; nSegGradRange = nAngGradRange * (int32_t)nQuality / 360; nSegGradRange = (nSegGradRange == 0) ? 1 : nSegGradRange; // Guard against div/0 bool bClockwise; int16_t nSegInd; int16_t nStepCnt; if (nSegEnd >= nSegStart) { nStepCnt = nSegEnd - nSegStart; bClockwise = true; } else { nStepCnt = nSegStart - nSegEnd; bClockwise = false; } #if defined(DBG_REDRAW) //GSLC_DEBUG_PRINT("FillSector: AngSecStart=%d AngSecEnd=%d SegStart=%d SegEnd=%d StepCnt=%d CW=%d\n", // nAngSecStart,nAngSecEnd,nSegStart,nSegEnd,nStepCnt,bClockwise); #endif for (int16_t nStepInd = 0; nStepInd < nStepCnt; nStepInd++) { // Remap from the step to the segment index, depending on direction nSegInd = (bClockwise)? (nSegStart + nStepInd) : (nSegStart - nStepInd - 1); nAng64 = (int32_t)(nSegInd * nStep64) % (int32_t)(360 * 64); #if defined(DBG_REDRAW) GSLC_DEBUG2_PRINT("FillSector: StepInd=%d SegInd=%d (%d..%d) Ang64=%d\n", nStepInd, nSegInd, nSegStart, nSegEnd, nAng64); #endif gslc_PolarToXY(nRad1, nAng64, &nX, &nY); anPts[0] = (gslc_tsPt) { nMidX + nX, nMidY + nY }; gslc_PolarToXY(nRad2, nAng64, &nX, &nY); anPts[1] = (gslc_tsPt) { nMidX + nX, nMidY + nY }; gslc_PolarToXY(nRad2, nAng64 + nStep64, &nX, &nY); anPts[2] = (gslc_tsPt) { nMidX + nX, nMidY + nY }; gslc_PolarToXY(nRad1, nAng64 + nStep64, &nX, &nY); anPts[3] = (gslc_tsPt) { nMidX + nX, nMidY + nY }; if (bGradient) { // Gradient coloring int16_t nGradPos = 1000 * (int32_t)(nSegInd-nSegGradStart) / nSegGradRange; #if defined(DBG_REDRAW) GSLC_DEBUG2_PRINT("FillSector: GradPos=%d\n", nGradPos); #endif colSeg = gslc_ColorBlend2(cArcStart, cArcEnd, 500, nGradPos); } else { // Flat coloring colSeg = cArcStart; } gslc_DrawFillQuad(pGui, anPts, colSeg); } } void gslc_DrawFillGradSector(gslc_tsGui* pGui, int16_t nQuality, int16_t nMidX, int16_t nMidY, int16_t nRad1, int16_t nRad2, gslc_tsColor cArcStart, gslc_tsColor cArcEnd, int16_t nAngSecStart, int16_t nAngSecEnd, int16_t nAngGradStart, int16_t nAngGradRange) { gslc_DrawFillSectorBase(pGui, nQuality, nMidX, nMidY, nRad1, nRad2, cArcStart, cArcEnd, true, nAngGradStart, nAngGradRange, nAngSecStart, nAngSecEnd); } void gslc_DrawFillSector(gslc_tsGui* pGui, int16_t nQuality, int16_t nMidX, int16_t nMidY, int16_t nRad1, int16_t nRad2, gslc_tsColor cArc, int16_t nAngSecStart, int16_t nAngSecEnd) { gslc_DrawFillSectorBase(pGui, nQuality, nMidX, nMidY, nRad1, nRad2, cArc, cArc, false, 0, 0, nAngSecStart, nAngSecEnd); } // ----------------------------------------------------------------------- // Font Functions // ----------------------------------------------------------------------- bool gslc_FontSetBase(gslc_tsGui* pGui, uint8_t nFontInd, int16_t nFontId, gslc_teFontRefType eFontRefType, const void* pvFontRef, uint16_t nFontSz) { if (nFontInd >= pGui->nFontMax) { GSLC_DEBUG2_PRINT("ERROR: FontSetBase() invalid Font index=%d\n",nFontInd); return false; } else { // Fetch a font resource from the driver const void* pvFont = gslc_DrvFontAdd(eFontRefType,pvFontRef,nFontSz); // FIXME: Resolve a means to detect if LINUX font files failed to load // and then return 'false'. Note that DrvFontAdd() may normally // return NULL in ADAGFX mode for some font types. gslc_ResetFont(&(pGui->asFont[nFontInd])); pGui->asFont[nFontInd].eFontRefType = eFontRefType; // TODO: Support specification of mode via FontAdd() API? pGui->asFont[nFontInd].eFontRefMode = GSLC_FONTREF_MODE_DEFAULT; pGui->asFont[nFontInd].pvFont = pvFont; pGui->asFont[nFontInd].nId = nFontId; pGui->asFont[nFontInd].nSize = nFontSz; return true; } } // Store font into indexed position in font storage // - nFontId must be in range 0..nFontMax-1 bool gslc_FontSet(gslc_tsGui* pGui, int16_t nFontId, gslc_teFontRefType eFontRefType, const void* pvFontRef, uint16_t nFontSz) { if ((nFontId < 0) || (nFontId >= pGui->nFontMax)) { GSLC_DEBUG2_PRINT("ERROR: FontSet() invalid Font ID=%d\n",nFontId); return false; } else { bool bRet = false; // The font index is set to the same as the font enum ID uint8_t nFontInd = nFontId; bRet = gslc_FontSetBase(pGui, nFontInd, nFontId, eFontRefType, pvFontRef, nFontSz); // Ensure the total font count is set to max pGui->nFontCnt = pGui->nFontMax; return bRet; } } bool gslc_FontAdd(gslc_tsGui* pGui,int16_t nFontId,gslc_teFontRefType eFontRefType, const void* pvFontRef,uint16_t nFontSz) { if (pGui->nFontCnt+1 > (pGui->nFontMax)) { GSLC_DEBUG2_PRINT("ERROR: FontAdd(%s) added too many fonts\n",""); return false; } else { bool bRet = false; // Fetch the next unallocated index uint8_t nFontInd = pGui->nFontCnt; bRet = gslc_FontSetBase(pGui, nFontInd, nFontId, eFontRefType, pvFontRef, nFontSz); // Increment the current font index pGui->nFontCnt++; return bRet; } } gslc_tsFont* gslc_FontGet(gslc_tsGui* pGui,int16_t nFontId) { uint8_t nFontInd; for (nFontInd=0;nFontIndnFontCnt;nFontInd++) { if (pGui->asFont[nFontInd].nId == nFontId) { return &(pGui->asFont[nFontInd]); } } return NULL; } bool gslc_FontSetMode(gslc_tsGui* pGui, int16_t nFontId, gslc_teFontRefMode eFontMode) { gslc_tsFont* pFont = NULL; pFont = gslc_FontGet(pGui, nFontId); if (!pFont) { // TODO: ERROR return false; } pFont->eFontRefMode = eFontMode; return true; } // ------------------------------------------------------------------------ // Page Functions // ------------------------------------------------------------------------ // Common event handler bool gslc_PageEvent(void* pvGui,gslc_tsEvent sEvent) { if (pvGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "PageEvent"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return false; } //gslc_tsGui* pGui = (gslc_tsGui*)(pvGui); //void* pvData = sEvent.pvData; gslc_tsPage* pPage = (gslc_tsPage*)(sEvent.pvScope); gslc_tsCollect* pCollect = NULL; if (pPage == NULL) { static const char GSLC_PMEM FUNCSTR[] = "PageEvent"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); } // Handle any page-level events first // ... // A Page only contains one Element Collection, so propagate // Handle the event types switch(sEvent.eType) { case GSLC_EVT_DRAW: case GSLC_EVT_TICK: case GSLC_EVT_TOUCH: pCollect = &pPage->sCollect; // Update scope reference & propagate sEvent.pvScope = (void*)(pCollect); gslc_CollectEvent(pvGui,sEvent); break; default: break; } // sEvent.eType return true; } void gslc_PageAdd(gslc_tsGui* pGui,int16_t nPageId,gslc_tsElem* psElem,uint16_t nMaxElem, gslc_tsElemRef* psElemRef,uint16_t nMaxElemRef) { if (pGui->nPageCnt+1 > (pGui->nPageMax)) { GSLC_DEBUG2_PRINT("ERROR: PageAdd(%s) added too many pages\n",""); return; } gslc_tsPage* pPage = &pGui->asPage[pGui->nPageCnt]; //pPage->pfuncXEvent = NULL; // UNUSED // Initialize pPage->sCollect gslc_CollectReset(&pPage->sCollect,psElem,nMaxElem,psElemRef,nMaxElemRef); // Assign the requested Page ID pPage->nPageId = nPageId; // Initialize the page elements bounds to empty pPage->rBounds = (gslc_tsRect) { 0, 0, 0, 0 }; // Increment the page count pGui->nPageCnt++; // Assign the first page added to be the current layer // in the page stack. This can be overridden later // with SetPageCur() if (gslc_GetPageCur(pGui) == GSLC_PAGE_NONE) { gslc_SetPageCur(pGui,nPageId); } // ------------------------------------------------------ // Force the page to redraw // TODO: Should this mark the page or the screen? gslc_PageRedrawSet(pGui,true); } int gslc_GetPageCur(gslc_tsGui* pGui) { gslc_tsPage* pStackPage = pGui->apPageStack[GSLC_STACK_CUR]; if (pStackPage == NULL) { return GSLC_PAGE_NONE; } return pStackPage->nPageId; } void gslc_SetStackPage(gslc_tsGui* pGui, uint8_t nStackPos, int16_t nPageId) { int16_t nPageSaved = GSLC_PAGE_NONE; gslc_tsPage* pPageSaved = pGui->apPageStack[nStackPos]; if (pPageSaved != NULL) { nPageSaved = pPageSaved->nPageId; } gslc_tsPage* pPage = NULL; if (nPageId == GSLC_PAGE_NONE) { // Disable the page #if defined(DEBUG_LOG) GSLC_DEBUG_PRINT("INFO: Disabled PageStack[%u]\n",nStackPos); #endif pPage = NULL; } else { // Find the page pPage = gslc_PageFindById(pGui, nPageId); if (pPage == NULL) { GSLC_DEBUG2_PRINT("ERROR: SetStackPage() can't find page (ID=%d)\n", nPageId); return; } } // Save a reference to the selected page pGui->apPageStack[nStackPos] = pPage; #if defined(DEBUG_LOG) GSLC_DEBUG_PRINT("INFO: Changed PageStack[%u] to page %u\n",nStackPos,nPageId); #endif // A change of page should always force a future redraw // TODO: Consider that showing a popup could potentially optimize out // the forced redraw step. if (nPageSaved != nPageId) { gslc_PageRedrawSet(pGui,true); } // Invalidate the old page in the stack if (pPageSaved != NULL) { gslc_InvalidateRgnPage(pGui, pPageSaved); } // Invalidate the new page in the stack and any above for (uint8_t nStackPage = nStackPos; nStackPage < GSLC_STACK__MAX; nStackPage++) { // Select the page collection to process pPage = pGui->apPageStack[nStackPage]; if (pPage != NULL) { gslc_InvalidateRgnPage(pGui, pPage); } } } void gslc_SetStackState(gslc_tsGui* pGui, uint8_t nStackPos, bool bActive, bool bDoDraw) { gslc_tsPage* pStackPage = pGui->apPageStack[nStackPos]; if (pStackPage == NULL) { // TODO: Error return; } pGui->abPageStackActive[nStackPos] = bActive; pGui->abPageStackDoDraw[nStackPos] = bDoDraw; } void gslc_SetPageBase(gslc_tsGui* pGui, int16_t nPageId) { gslc_SetStackPage(pGui, GSLC_STACK_BASE, nPageId); } void gslc_SetPageCur(gslc_tsGui* pGui,int16_t nPageId) { gslc_SetStackPage(pGui, GSLC_STACK_CUR, nPageId); } void gslc_SetPageOverlay(gslc_tsGui* pGui,int16_t nPageId) { gslc_SetStackPage(pGui, GSLC_STACK_OVERLAY, nPageId); } void gslc_PopupShow(gslc_tsGui* pGui, int16_t nPageId, bool bModal) { gslc_SetStackPage(pGui, GSLC_STACK_OVERLAY, nPageId); // If modal dialog selected, then deactivate other pages in stack // If modeless dialog selected, then don't deactivate other pages in stack if (bModal) { // Modal: deactive other pages and disable redraw gslc_SetStackState(pGui, GSLC_STACK_CUR, false, false); gslc_SetStackState(pGui, GSLC_STACK_BASE, false, false); } else { // Modeless: activate other pages and enable background redraw // NOTE: If a popup overlaps controls that continue to be updated // in the background, then extra redraws will occur. In that // case, it may be preferrable to set redraw to false. gslc_SetStackState(pGui, GSLC_STACK_CUR, true, true); gslc_SetStackState(pGui, GSLC_STACK_BASE, true, true); } } void gslc_PopupHide(gslc_tsGui* pGui) { gslc_SetStackPage(pGui, GSLC_STACK_OVERLAY, GSLC_PAGE_NONE); // Ensure other pages in stack are activated // - This is done in case they were deactivated due to a modal popup gslc_SetStackState(pGui, GSLC_STACK_CUR, true, true); gslc_SetStackState(pGui, GSLC_STACK_BASE, true, true); } // Adjust the flag that indicates whether the entire page // requires a redraw. void gslc_PageRedrawSet(gslc_tsGui* pGui,bool bRedraw) { if (pGui == NULL) { return; } pGui->bScreenNeedRedraw = bRedraw; } bool gslc_PageRedrawGet(gslc_tsGui* pGui) { if (pGui == NULL) { return false; } return pGui->bScreenNeedRedraw; } // Check the redraw flag on all elements on the current page and update // the redraw status if additional redraws are required (or the // entire page should be marked as requiring redraw). // - The typical case for this being required is when an element // requires redraw but it is marked as being transparent. Therefore, // the lower level elements should be redrawn. // - For now, just mark the entire page as requiring redraw. // TODO: Determine which elements underneath should be redrawn based // on the region exposed by the transparent element. void gslc_PageRedrawCalc(gslc_tsGui* pGui) { int nInd; int nStackPage; gslc_tsElem* pElem = NULL; gslc_tsElemRef* pElemRef = NULL; gslc_tsCollect* pCollect = NULL; bool bRedrawFullPage = false; // Does entire page require redraw? gslc_tsPage* pPage = NULL; // Work on each enabled page in the stack for (nStackPage=0;nStackPageapPageStack[nStackPage]; if (!pPage) { // If this stack page is not enabled, skip to next stack page continue; } if (!pGui->abPageStackDoDraw[nStackPage]) { // If this stack page has redraw disabled, skip full-page redraw check continue; } pCollect = &pPage->sCollect; for (nInd=0;nIndnElemRefCnt;nInd++) { pElemRef = &pCollect->asElemRef[nInd]; gslc_teElemRefFlags eFlags = pElemRef->eElemFlags; pElem = gslc_GetElemFromRef(pGui,pElemRef); //GSLC_DEBUG2_PRINT("PageRedrawCalc: Ind=%u ID=%u redraw=%u flags_old=%u fea=%u\n",nInd,pElem->nId, // (eFlags & GSLC_ELEMREF_REDRAW_MASK),eFlags,pElem->nFeatures); if ((eFlags & GSLC_ELEMREF_REDRAW_MASK) != GSLC_ELEMREF_REDRAW_NONE) { // If partial redraw is supported, then we // look out for transparent elements which may // still warrant full page redraw. if (pGui->bRedrawPartialEn) { // Is the element transparent? // FIXME: Instead of forcing full page redraw, consider // using clipping region for partial redraw if (!(pElem->nFeatures & GSLC_ELEM_FEA_FILL_EN)) { bRedrawFullPage = true; } } else { bRedrawFullPage = true; } if (bRedrawFullPage) { // Determined that full page needs redraw // so no need to check any more elements break; } } } } // nStackPage if (bRedrawFullPage) { // Mark the entire screen as requiring redraw gslc_PageRedrawSet(pGui,true); } } // Redraw the active page // - If the page has been marked as needing redraw, then all // elements are rendered // - If the page has not been marked as needing redraw then only // the elements that have been marked as needing redraw // are rendered. void gslc_PageRedrawGo(gslc_tsGui* pGui) { // Update any page redraw status that may be required // - Note that this routine handles cases where an element // marked as requiring update is semi-transparent which can // cause other elements to be redrawn as well. gslc_PageRedrawCalc(pGui); // Determine final state of full-screen redraw bool bPageRedraw = gslc_PageRedrawGet(pGui); // Set the clipping based on the current invalidated region if (pGui->bInvalidateEn) { #if defined(DBG_REDRAW) // Note that this will still outline the invalidation region // even if we later discover that the changed element is on // a page in the stack that has been disabled through // abPageStackDoDraw[] = false. GSLC_DEBUG_PRINT("DBG: PageRedrawGo() InvRgn: En=%d (%d,%u)-(%d,%d) PageRedraw=%d\n", pGui->bInvalidateEn, pGui->rInvalidateRect.x, pGui->rInvalidateRect.y, pGui->rInvalidateRect.x + pGui->rInvalidateRect.w - 1, pGui->rInvalidateRect.y + pGui->rInvalidateRect.h - 1, bPageRedraw); // Mark the invalidation region gslc_DrvDrawFrameRect(pGui, pGui->rInvalidateRect, GSLC_COL_RED); // Slow down rendering delay(1000); #endif // DBG_REDRAW gslc_SetClipRect(pGui, &(pGui->rInvalidateRect)); } else { // No invalidation region defined, so default the // clipping region to the entire display gslc_SetClipRect(pGui, NULL); } // If a full page redraw is required, then start by // redrawing the background. // NOTE: // - It would be cleaner if we could treat the background // layer like any other element (and hence check for its // need-redraw status). // - For now, assume background doesn't need update except // if the entire page is to be redrawn // TODO: Fix this assumption (either add specific flag // for bBkgndNeedRedraw or make the background just // another element). if (bPageRedraw) { gslc_DrvDrawBkgnd(pGui); gslc_PageFlipSet(pGui,true); } // Draw other elements (as needed, unless forced page redraw) // TODO: Handle GSLC_EVTSUB_DRAW_NEEDED uint32_t nSubType = (bPageRedraw)?GSLC_EVTSUB_DRAW_FORCE:GSLC_EVTSUB_DRAW_NEEDED; void* pvData = NULL; // TODO: Consider creating a flag that indicates whether any elements // on the page have requested redraw. This would enable us to skip // over this exhaustive search every time we call Update() // Issue page redraw events to all pages in stack // - Start from bottom page in stack first for (int nStackPage = 0; nStackPage < GSLC_STACK__MAX; nStackPage++) { gslc_tsPage* pStackPage = pGui->apPageStack[nStackPage]; if (!pStackPage) { continue; } if (!bPageRedraw && !pGui->abPageStackDoDraw[nStackPage]) { // When doing a full page redraw, proceed as normal // When only doing a parital page redraw, check to see if // the page has been marked as redraw-disabled. If so, skip // updating the elements on the page. // // The redraw-disabled mode is useful to prevent "show-through" // from dynamically-updating elements in lower layers of the // page stack (this may occur with popup dialogs). If the overlay // page does not overlap dynamically-updating elements, then // DoDraw can be set to true, enabling background updates to occur. continue; } pvData = (void*)(pStackPage); gslc_tsEvent sEvent = gslc_EventCreate(pGui,GSLC_EVT_DRAW,nSubType,pvData,NULL); gslc_PageEvent(pGui,sEvent); } // Clear the page redraw flag gslc_PageRedrawSet(pGui,false); // Reset the invalidated regions gslc_InvalidateRgnReset(pGui); // Restore the clipping region to the entire display gslc_SetClipRect(pGui, NULL); // Page flip the entire screen // - TODO: We could also call Update instead of Flip as that would // limit the region to refresh. gslc_PageFlipGo(pGui); } void gslc_PageFlipSet(gslc_tsGui* pGui,bool bNeeded) { if (pGui == NULL) { return; } pGui->bScreenNeedFlip = bNeeded; // To assist in debug of drawing primitives, support immediate // rendering of the current display. Note that this only works // for display drivers that don't invalidate the double-buffer (eg. SDL1.2) #ifdef DBG_DRAW_IMM gslc_DrvPageFlipNow(pGui); #endif } bool gslc_PageFlipGet(gslc_tsGui* pGui) { if (pGui == NULL) { return false; } return pGui->bScreenNeedFlip; } void gslc_PageFlipGo(gslc_tsGui* pGui) { if (pGui == NULL) { return; } if (pGui->bScreenNeedFlip) { gslc_DrvPageFlipNow(pGui); // Indicate that page flip is no longer required gslc_PageFlipSet(pGui,false); } } gslc_tsPage* gslc_PageFindById(gslc_tsGui* pGui,int16_t nPageId) { int8_t nInd; // Loop through list of pages // Return pointer to page gslc_tsPage* pFoundPage = NULL; for (nInd=0;nIndnPageMax;nInd++) { if (pGui->asPage[nInd].nPageId == nPageId) { pFoundPage = &pGui->asPage[nInd]; break; } } // Error handling: if not found, make this a fatal error // as it shows a serious config error and continued operation // is not viable. if (pFoundPage == NULL) { GSLC_DEBUG2_PRINT("ERROR: PageFindById() can't find page (ID=%d)\n",nPageId); return NULL; } return pFoundPage; } gslc_tsElemRef* gslc_PageFindElemById(gslc_tsGui* pGui,int16_t nPageId,int16_t nElemId) { gslc_tsPage* pPage = NULL; gslc_tsElemRef* pElemRef = NULL; // Get the page pPage = gslc_PageFindById(pGui,nPageId); if (pPage == NULL) { GSLC_DEBUG2_PRINT("ERROR: PageFindElemById() can't find page (ID=%d)\n",nPageId); return NULL; } // Find the element reference in the page's element collection pElemRef = gslc_CollectFindElemById(pGui,&pPage->sCollect,nElemId); return pElemRef; } /* UNUSED void gslc_PageSetEventFunc(gslc_tsGui* pGui,gslc_tsPage* pPage,GSLC_CB_EVENT funcCb) { if ((pPage == NULL) || (funcCb == NULL)) { static const char GSLC_PMEM FUNCSTR[] = "PageSetEventFunc"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } pPage->pfuncXEvent = funcCb; } */ // TODO: Create a PageStackFocusStep() // - The functionality would track which page is currently focused // and instead of looping around on the page, the next enabled // page in the stack would be traversed. // - Wrapping at the bottom of the stack would create the GSLC_IND_NONE // state. // - Without this enhancement, GPIO / keyboard controls will only // operate on the top-most page in the stack, which prevents the // ability to navigate tab select controls if they are provided // by a lower layer in the stack. int16_t gslc_PageFocusStep(gslc_tsGui* pGui, gslc_tsPage* pPage, bool bNext) { #if !(GSLC_FEATURE_INPUT) return GSLC_IND_NONE; #else bool bWrapped = false; bool bFound = false; int16_t nInd = GSLC_IND_NONE; gslc_tsCollect* pCollect = &pPage->sCollect; bFound = gslc_CollectFindFocusStep(pGui, pCollect, bNext, &bWrapped, &nInd); if (!bFound) { nInd = GSLC_IND_NONE; } else { if (bWrapped) { // Optionally pause here // If we wrap, then disable current focus for this particular step nInd = GSLC_IND_NONE; } } gslc_CollectSetFocus(pGui,pCollect,nInd); return nInd; #endif } // ------------------------------------------------------------------------ // Element General Functions // ------------------------------------------------------------------------ int gslc_ElemGetId(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return GSLC_ID_NONE; return pElem->nId; } // Get a flag from an element reference // Note that the flag remains aligned to the position within the flags bitfield uint8_t gslc_GetElemRefFlag(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,uint8_t nFlagMask) { if (!pElemRef) { static const char GSLC_PMEM FUNCSTR[] = "GetElemRefFlag"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return 0; } return pElemRef->eElemFlags & nFlagMask; } // Set a flag in an element reference // Note that the nFlagVal must be aligned to the nFlagMask void gslc_SetElemRefFlag(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,uint8_t nFlagMask,uint8_t nFlagVal) { if (!pElemRef) { static const char GSLC_PMEM FUNCSTR[] = "SetElemRefFlag"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } uint8_t nCurFlags = pElemRef->eElemFlags; uint8_t nNewFlags = (nCurFlags & ~nFlagMask) | nFlagVal; pElemRef->eElemFlags = nNewFlags; } // Returns a pointer to an element from an element reference // - Handle caching from FLASH if element is accessed via PROGMEM gslc_tsElem* gslc_GetElemFromRef(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { if (!pElemRef) { static const char GSLC_PMEM FUNCSTR[] = "GetElemFromRef"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return NULL; } gslc_teElemRefFlags eFlags = pElemRef->eElemFlags; gslc_tsElem* pElem = pElemRef->pElem; // NOTE below // NOTE: The pElem assignment above is not applicable for // PROGMEM-based elements. For these elements, we // need to access the structure through the FLASH APIs, // hence the default assignment above will be overwritten // below. // If the element is in FLASH and requires PROGMEM to access // then cache it locally and return a pointer to the global // temporary element instead so that further accesses can // be direct. if ((eFlags & GSLC_ELEMREF_SRC) == GSLC_ELEMREF_SRC_PROG) { // TODO: Optimize by checking to see if we already cached this element // - Can probably do this by comparing the bValid and nId #if (GSLC_USE_PROGMEM) memcpy_P(&pGui->sElemTmpProg,pElem,sizeof(gslc_tsElem)); pElem = &pGui->sElemTmpProg; #endif } else if ((eFlags & GSLC_ELEMREF_SRC) == GSLC_ELEMREF_SRC_CONST) { // We are running on device that may support FLASH storage // simply by using a const declaration. No special // PROGMEM access mechanism is required, therefore it is // not necessary to copy to the temporary RAM element. // An example device using this mode is Cortex-M0. // No explicit copy from FLASH is required. } else { // SRC_ELEMREF_SRC_RAM // No copy required } return pElem; } // Fetch element from reference, with debug gslc_tsElem* gslc_GetElemFromRefD(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int16_t nLineNum) { if (pElemRef == NULL) { GSLC_DEBUG2_PRINT("ERROR: GetElemFromRefD(Line %d) pElemRef is NULL\n", nLineNum); return NULL; } gslc_tsElem* pElem = gslc_GetElemFromRef(pGui, pElemRef); if (pElem == NULL) { GSLC_DEBUG2_PRINT("ERROR: GetElemFromRefD(Line %d) pElem is NULL\n", nLineNum); return NULL; } return pElem; } // For extended elements, fetch the extended data structure void* gslc_GetXDataFromRef(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int16_t nType, int16_t nLineNum) { if (pElemRef == NULL) { GSLC_DEBUG_PRINT("ERROR: GetXDataFromRef(Type %d, Line %d) pElemRef is NULL\n", nType, nLineNum); return NULL; } gslc_tsElem* pElem = gslc_GetElemFromRef(pGui, pElemRef); if (pElem == NULL) { GSLC_DEBUG_PRINT("ERROR: GetXDataFromRef(Type %d, Line %d) pElem is NULL\n", nType, nLineNum); return NULL; } if (pElem->nType != nType) { GSLC_DEBUG_PRINT("ERROR: GetXDataFromRef(Type %d, Line %d) Elem type mismatch\n", nType, nLineNum); return NULL; } void* pXData = pElem->pXData; if (pXData == NULL) { GSLC_DEBUG_PRINT("ERROR: GetXDataFromRef(Type %d, Line %d) pXData is NULL\n", nType, nLineNum); return NULL; } return pXData; } // ------------------------------------------------------------------------ // Element Global Functions // ------------------------------------------------------------------------ // Set the global rounded radius for rounded rectangles void gslc_SetRoundRadius(gslc_tsGui* pGui,uint8_t nRadius) { pGui->nRoundRadius = (int16_t)nRadius; // Update redraw flag gslc_PageRedrawSet(pGui,true); } // ------------------------------------------------------------------------ // Element Creation Functions // ------------------------------------------------------------------------ gslc_tsElemRef* gslc_ElemCreateTxt(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage,gslc_tsRect rElem, char* pStrBuf,uint8_t nStrBufMax,int16_t nFontId) { gslc_tsElem sElem; gslc_tsElemRef* pElemRef = NULL; sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPE_TXT,rElem,pStrBuf,nStrBufMax,nFontId); sElem.colElemFill = GSLC_COL_BLACK; sElem.colElemFillGlow = GSLC_COL_BLACK; sElem.colElemFrame = GSLC_COL_GRAY; sElem.colElemFrameGlow = GSLC_COL_GRAY; sElem.colElemText = GSLC_COL_YELLOW; sElem.colElemTextGlow = GSLC_COL_YELLOW; sElem.nFeatures |= GSLC_ELEM_FEA_FILL_EN; sElem.eTxtAlign = GSLC_ALIGN_MID_LEFT; if (nPage != GSLC_PAGE_NONE) { pElemRef = gslc_ElemAdd(pGui,nPage,&sElem,GSLC_ELEMREF_DEFAULT); return pElemRef; #if (GSLC_FEATURE_COMPOUND) } else { // Save as temporary element pGui->sElemTmp = sElem; pGui->sElemRefTmp.pElem = &(pGui->sElemTmp); pGui->sElemRefTmp.eElemFlags = GSLC_ELEMREF_DEFAULT | GSLC_ELEMREF_REDRAW_FULL; return &(pGui->sElemRefTmp); #endif } return NULL; } gslc_tsElemRef* gslc_ElemCreateBtnTxt(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage, gslc_tsRect rElem,char* pStrBuf,uint8_t nStrBufMax,int16_t nFontId,GSLC_CB_TOUCH cbTouch) { gslc_tsElem sElem; gslc_tsElemRef* pElemRef = NULL; // Ensure the Font has been defined if (gslc_FontGet(pGui,nFontId) == NULL) { GSLC_DEBUG2_PRINT("ERROR: ElemCreateBtnTxt(ID=%d): Font(ID=%d) not loaded\n",nElemId,nFontId); return NULL; } sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPE_BTN,rElem,pStrBuf,nStrBufMax,nFontId); sElem.colElemFill = GSLC_COL_BLUE_DK4; sElem.colElemFillGlow = GSLC_COL_BLUE_DK1; sElem.colElemFrame = GSLC_COL_BLUE_DK2; sElem.colElemFrameGlow = GSLC_COL_BLUE_DK2; sElem.colElemText = GSLC_COL_WHITE; sElem.colElemTextGlow = GSLC_COL_WHITE; sElem.nFeatures |= GSLC_ELEM_FEA_FRAME_EN; sElem.nFeatures |= GSLC_ELEM_FEA_FILL_EN; sElem.nFeatures |= GSLC_ELEM_FEA_CLICK_EN; sElem.nFeatures |= GSLC_ELEM_FEA_GLOW_EN; sElem.pfuncXTouch = cbTouch; if (nPage != GSLC_PAGE_NONE) { pElemRef = gslc_ElemAdd(pGui,nPage,&sElem,GSLC_ELEMREF_DEFAULT); return pElemRef; #if (GSLC_FEATURE_COMPOUND) } else { // Save as temporary element pGui->sElemTmp = sElem; pGui->sElemRefTmp.pElem = &(pGui->sElemTmp); pGui->sElemRefTmp.eElemFlags = GSLC_ELEMREF_DEFAULT | GSLC_ELEMREF_REDRAW_FULL; return &(pGui->sElemRefTmp); #endif } return NULL; } gslc_tsElemRef* gslc_ElemCreateBtnImg(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage, gslc_tsRect rElem,gslc_tsImgRef sImgRef,gslc_tsImgRef sImgRefSel,GSLC_CB_TOUCH cbTouch) { gslc_tsElem sElem; gslc_tsElemRef* pElemRef = NULL; sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPE_BTN,rElem,NULL,0,GSLC_FONT_NONE); sElem.colElemFill = GSLC_COL_BLACK; sElem.colElemFillGlow = GSLC_COL_BLACK; sElem.colElemFrame = GSLC_COL_BLUE_DK2; sElem.colElemFrameGlow = GSLC_COL_BLUE_DK2; sElem.nFeatures &= ~GSLC_ELEM_FEA_FRAME_EN; sElem.nFeatures |= GSLC_ELEM_FEA_FILL_EN; sElem.nFeatures |= GSLC_ELEM_FEA_CLICK_EN; sElem.nFeatures |= GSLC_ELEM_FEA_GLOW_EN; sElem.pfuncXTouch = cbTouch; // Update the normal and glowing images gslc_DrvSetElemImageNorm(pGui,&sElem,sImgRef); gslc_DrvSetElemImageGlow(pGui,&sElem,sImgRefSel); if (nPage != GSLC_PAGE_NONE) { pElemRef = gslc_ElemAdd(pGui,nPage,&sElem,GSLC_ELEMREF_DEFAULT); return pElemRef; #if (GSLC_FEATURE_COMPOUND) } else { // Save as temporary element pGui->sElemTmp = sElem; pGui->sElemRefTmp.pElem = &(pGui->sElemTmp); pGui->sElemRefTmp.eElemFlags = GSLC_ELEMREF_DEFAULT | GSLC_ELEMREF_REDRAW_FULL; return &(pGui->sElemRefTmp); #endif } return NULL; } gslc_tsElemRef* gslc_ElemCreateBox(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage,gslc_tsRect rElem) { gslc_tsElem sElem; gslc_tsElemRef* pElemRef = NULL; sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPE_BOX,rElem,NULL,0,GSLC_FONT_NONE); sElem.colElemFill = GSLC_COL_BLACK; sElem.colElemFillGlow = GSLC_COL_BLACK; sElem.colElemFrame = GSLC_COL_GRAY; sElem.colElemFrameGlow = GSLC_COL_GRAY; sElem.nFeatures |= GSLC_ELEM_FEA_FILL_EN; sElem.nFeatures |= GSLC_ELEM_FEA_FRAME_EN; if (nPage != GSLC_PAGE_NONE) { pElemRef = gslc_ElemAdd(pGui,nPage,&sElem,GSLC_ELEMREF_DEFAULT); return pElemRef; #if (GSLC_FEATURE_COMPOUND) } else { // Save as temporary element pGui->sElemTmp = sElem; pGui->sElemRefTmp.pElem = &(pGui->sElemTmp); pGui->sElemRefTmp.eElemFlags = GSLC_ELEMREF_DEFAULT | GSLC_ELEMREF_REDRAW_FULL; return &(pGui->sElemRefTmp); #endif } return NULL; } gslc_tsElemRef* gslc_ElemCreateLine(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage,int16_t nX0,int16_t nY0,int16_t nX1,int16_t nY1) { gslc_tsElem sElem; gslc_tsElemRef* pElemRef = NULL; gslc_tsRect rRect; rRect.x = nX0; rRect.y = nY0; rRect.w = nX1 - nX0 + 1; rRect.h = nY1 - nY0 + 1; sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPE_LINE,rRect,NULL,0,GSLC_FONT_NONE); // For line elements, we will draw it with the "fill" color sElem.colElemFill = GSLC_COL_GRAY; sElem.colElemFillGlow = GSLC_COL_GRAY; sElem.nFeatures &= ~GSLC_ELEM_FEA_FILL_EN; // Disable boundary box fill sElem.nFeatures &= ~GSLC_ELEM_FEA_FRAME_EN; // Disable boundary box frame if (nPage != GSLC_PAGE_NONE) { pElemRef = gslc_ElemAdd(pGui,nPage,&sElem,GSLC_ELEMREF_DEFAULT); return pElemRef; #if (GSLC_FEATURE_COMPOUND) } else { // Save as temporary element pGui->sElemTmp = sElem; pGui->sElemRefTmp.pElem = &(pGui->sElemTmp); pGui->sElemRefTmp.eElemFlags = GSLC_ELEMREF_DEFAULT | GSLC_ELEMREF_REDRAW_FULL; return &(pGui->sElemRefTmp); #endif } return NULL; } gslc_tsElemRef* gslc_ElemCreateImg(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage, gslc_tsRect rElem,gslc_tsImgRef sImgRef) { gslc_tsElem sElem; gslc_tsElemRef* pElemRef = NULL; sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPE_BOX,rElem,NULL,0,GSLC_FONT_NONE); sElem.nFeatures &= ~GSLC_ELEM_FEA_FRAME_EN; sElem.nFeatures |= GSLC_ELEM_FEA_FILL_EN; sElem.nFeatures &= ~GSLC_ELEM_FEA_CLICK_EN; // Update the normal and glowing images gslc_DrvSetElemImageNorm(pGui,&sElem,sImgRef); gslc_DrvSetElemImageGlow(pGui,&sElem,sImgRef); if (nPage != GSLC_PAGE_NONE) { pElemRef = gslc_ElemAdd(pGui,nPage,&sElem,GSLC_ELEMREF_DEFAULT); return pElemRef; #if (GSLC_FEATURE_COMPOUND) } else { // Save as temporary element pGui->sElemTmp = sElem; pGui->sElemRefTmp.pElem = &(pGui->sElemTmp); pGui->sElemRefTmp.eElemFlags = GSLC_ELEMREF_DEFAULT | GSLC_ELEMREF_REDRAW_FULL; return &(pGui->sElemRefTmp); #endif } return NULL; } // ------------------------------------------------------------------------ // Element Event Handlers // ------------------------------------------------------------------------ // Common event handler bool gslc_ElemEvent(void* pvGui,gslc_tsEvent sEvent) { if (pvGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemEvent"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return false; } gslc_tsGui* pGui = (gslc_tsGui*)(pvGui); void* pvData = sEvent.pvData; void* pvScope = sEvent.pvScope; gslc_tsElemRef* pElemRef = NULL; gslc_tsElemRef* pElemRefTracked = NULL; gslc_tsElem* pElem = NULL; gslc_tsElem* pElemTracked = NULL; gslc_tsEventTouch* pTouchRec = NULL; int nRelX,nRelY; gslc_teTouch eTouch; GSLC_CB_TOUCH pfuncXTouch = NULL; GSLC_CB_TICK pfuncXTick = NULL; switch(sEvent.eType) { case GSLC_EVT_DRAW: // Fetch the parameters pElemRef = (gslc_tsElemRef*)(pvScope); pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); // Determine if redraw is needed gslc_teRedrawType eRedraw = gslc_ElemGetRedraw(pGui,pElemRef); if (sEvent.nSubType == GSLC_EVTSUB_DRAW_FORCE) { // Despite the current pending redraw state of the element, // we will force a full redraw as requested. //GSLC_DEBUG_PRINT("DBG: ElemEvent(Draw) nId=%d eRedraw=%d: force to FULL\n",pElem->nId,eRedraw); return gslc_ElemDrawByRef(pGui,pElemRef,GSLC_REDRAW_FULL); } else if (eRedraw != GSLC_REDRAW_NONE) { // There is a pending redraw for the element. It may // either be an incremental or full redraw. //GSLC_DEBUG_PRINT("DBG: ElemEvent(Draw) nId=%d eRedraw=%d\n",pElem->nId,eRedraw); return gslc_ElemDrawByRef(pGui,pElemRef,eRedraw); } else { // No redraw needed pending return true; } break; case GSLC_EVT_TOUCH: #if defined(DRV_TOUCH_NONE) // Hide warnings about unused variables when TOUCH is not enabled (void)pvData; (void)pElemRefTracked; (void)pElemTracked; (void)pTouchRec; (void)nRelX; (void)nRelY; (void)eTouch; (void)pfuncXTouch; #else // Fetch the parameters pElemRef = (gslc_tsElemRef*)(pvScope); pTouchRec = (gslc_tsEventTouch*)(pvData); pElemRefTracked = pElemRef; pElemTracked = gslc_GetElemFromRef(pGui,pElemRefTracked); eTouch = pTouchRec->eTouch; if ((eTouch & GSLC_TOUCH_TYPE_MASK) == GSLC_TOUCH_DIRECT) { // Pass parameters directly (they are not coordinates) nRelX = pTouchRec->nX; nRelY = pTouchRec->nY; } else { // Generate relative coordinates nRelX = pTouchRec->nX - pElemTracked->rElem.x; nRelY = pTouchRec->nY - pElemTracked->rElem.y; } // Since we are going to use the callback within the element // we need to ensure it is cached in RAM first pElemTracked = gslc_GetElemFromRef(pGui,pElemRefTracked); pfuncXTouch = pElemTracked->pfuncXTouch; // Invoke the callback function if (pfuncXTouch != NULL) { // Pass in the relative position from corner of element region (*pfuncXTouch)(pvGui,(void*)(pElemRefTracked),eTouch,nRelX,nRelY); } #endif // DRV_TOUCH_NONE break; case GSLC_EVT_TICK: // Fetch the parameters pElemRef = (gslc_tsElemRef*)(pvScope); // Since we are going to use the callback within the element // we need to ensure it is cached in RAM first pElem = gslc_GetElemFromRef(pGui,pElemRef); pfuncXTick = pElem->pfuncXTick; // Invoke the callback function if (pfuncXTick != NULL) { // TODO: Confirm that tick functions want pvScope (*pfuncXTick)(pvGui,(void*)(pElemRef)); return true; } break; default: break; } return true; } // ------------------------------------------------------------------------ // Element Drawing Functions // ------------------------------------------------------------------------ // Draw an element to the active display // - Element is referenced by page ID and element ID // - Tnis routine triggers an immediate redraw of an element, and may be useful // for user code in creating custom drawing callbacks void gslc_ElemDraw(gslc_tsGui* pGui,int16_t nPageId,int16_t nElemId) { gslc_tsElemRef* pElemRef = gslc_PageFindElemById(pGui,nPageId,nElemId); gslc_tsEvent sEvent = gslc_EventCreate(pGui,GSLC_EVT_DRAW,GSLC_EVTSUB_DRAW_FORCE,(void*)pElemRef,NULL); gslc_ElemEvent(pGui,sEvent); } void gslc_DrawTxtBase(gslc_tsGui* pGui, char* pStrBuf,gslc_tsRect rTxt,gslc_tsFont* pTxtFont,gslc_teTxtFlags eTxtFlags, int8_t eTxtAlign,gslc_tsColor colTxt,gslc_tsColor colBg,int16_t nMarginW,int16_t nMarginH) { int16_t nElemX,nElemY; uint16_t nElemW,nElemH; nElemX = rTxt.x; nElemY = rTxt.y; nElemW = rTxt.w; nElemH = rTxt.h; // Overlay the text bool bRenderTxt = true; // Skip text render if buffer pointer not allocated if ((bRenderTxt) && (pStrBuf == NULL)) { bRenderTxt = false; } // Skip text render if string is not set if ((bRenderTxt) && ((eTxtFlags & GSLC_TXT_ALLOC) == GSLC_TXT_ALLOC_NONE)) { bRenderTxt = false; } // Do we still want to render? if (bRenderTxt) { #if (DRV_HAS_DRAW_TEXT) // Determine if GUIslice or driver should perform text alignment // - Generally, GUIslice provides the text alignment functionality // by querying the display driver for the dimensions of the text // to be rendered. // - Some drivers (such as TFT_eSPI) perform more complex logic // associated with the text alignment and hence GUIslice will // defer to the driver to handle the positioning. In that case // the bounding box and alignment mode is provided to the driver. #if (DRV_OVERRIDE_TXT_ALIGN) // GUIslice will allow the driver to perform the text alignment // calculations. // Provide bounding box and alignment flag to driver to calculate int16_t nX0 = nElemX + nMarginW; int16_t nY0 = nElemY + nMarginH; int16_t nX1 = nX0 + nElemW - 2*nMarginW; int16_t nY1 = nY0 + nElemH - 2*nMarginH; gslc_DrvDrawTxtAlign(pGui,nX0,nY0,nX1,nY1,eTxtAlign,pTxtFont, pStrBuf,eTxtFlags,colTxt,colBg); #else // DRV_OVERRIDE_TXT_ALIGN // GUIslice will ask the driver for the text dimensions and calculate // the appropriate positioning to support the requested text // alignment mode. // Fetch the size of the text to allow for justification // NOTE: For multi-line text strings, the following call will // return the maximum dimensions of the entire block of // text, thus alignment will be based on the outer dimensions // not individual rows of text. As a result, the overall // text block will be rendered with the requested alignment // but individual rows will render like GSLC_ALIGNH_LEFT // within the aligned text block. In order to support per-line // horizontal justification, a pre-scan and alignment calculation // for each text row would need to be performed. int16_t nTxtOffsetX=0; int16_t nTxtOffsetY=0; uint16_t nTxtSzW=0; uint16_t nTxtSzH=0; gslc_DrvGetTxtSize(pGui,pTxtFont,pStrBuf,eTxtFlags,&nTxtOffsetX,&nTxtOffsetY,&nTxtSzW,&nTxtSzH); // Calculate the text alignment int16_t nTxtX,nTxtY; // Check for ALIGNH_LEFT & ALIGNH_RIGHT. Default to ALIGNH_MID if (eTxtAlign & GSLC_ALIGNH_LEFT) { nTxtX = nElemX+nMarginW; } else if (eTxtAlign & GSLC_ALIGNH_RIGHT) { nTxtX = nElemX+nElemW-nMarginW-nTxtSzW; } else { nTxtX = nElemX+(nElemW/2)-(nTxtSzW/2); } // Check for ALIGNV_TOP & ALIGNV_BOT. Default to ALIGNV_MID if (eTxtAlign & GSLC_ALIGNV_TOP) { nTxtY = nElemY+nMarginH; } else if (eTxtAlign & GSLC_ALIGNV_BOT) { nTxtY = nElemY+nElemH-nMarginH-nTxtSzH; } else { nTxtY = nElemY+(nElemH/2)-(nTxtSzH/2); } // Now correct for offset from text bounds // - This is used by the driver (such as Adafruit-GFX) to provide an // adjustment for baseline height, etc. nTxtX += nTxtOffsetX; nTxtY -= nTxtOffsetY; // Call the driver text rendering routine gslc_DrvDrawTxt(pGui,nTxtX,nTxtY,pTxtFont,pStrBuf,eTxtFlags,colTxt,colBg); #endif // DRV_OVERRIDE_TXT_ALIGN #else // DRV_HAS_DRAW_TEXT // No text support in driver, so skip #endif // DRV_HAS_DRAW_TEXT } } // Draw an element to the active display // - Element is referenced by an element pointer // - TODO: Handle GSLC_TYPE_BKGND bool gslc_ElemDrawByRef(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_teRedrawType eRedraw) { if (eRedraw == GSLC_REDRAW_NONE) { // No redraw to do return true; } gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return false; // -------------------------------------------------------------------------- // Handle visibility // -------------------------------------------------------------------------- bool bVisible = gslc_ElemGetVisible(pGui, pElemRef); if (!bVisible) { // The element is hidden, so no drawing required. // Clear the redraw-pending flag. gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_NONE); return true; } // -------------------------------------------------------------------------- // Custom drawing // -------------------------------------------------------------------------- // Handle any extended element types // - If the pfuncXDraw callback is defined, then let the callback // function supersede all default handling here // - Note that the end of the callback function is expected // to clear the redraw flag if (pElem->pfuncXDraw != NULL) { (*pElem->pfuncXDraw)((void*)(pGui),(void*)(pElemRef),eRedraw); return true; } // -------------------------------------------------------------------------- // Init for default drawing // -------------------------------------------------------------------------- bool bGlowEn,bGlowing,bGlowNow; int16_t nElemX,nElemY; uint16_t nElemW,nElemH; nElemX = pElem->rElem.x; nElemY = pElem->rElem.y; nElemW = pElem->rElem.w; nElemH = pElem->rElem.h; bGlowEn = pElem->nFeatures & GSLC_ELEM_FEA_GLOW_EN; // Does the element support glow state? bGlowing = gslc_ElemGetGlow(pGui,pElemRef); // Element should be glowing (if enabled) bGlowNow = bGlowEn & bGlowing; // Element is currently glowing gslc_tsColor colBg = GSLC_COL_BLACK; // -------------------------------------------------------------------------- // Background // -------------------------------------------------------------------------- // Fill in the background gslc_tsRect rElemInner = pElem->rElem; // - If both fill and frame are enabled then contract // the fill region slightly so that we don't overdraw // the frame (prevent unnecessary flicker). if ((pElem->nFeatures & GSLC_ELEM_FEA_FILL_EN) && (pElem->nFeatures & GSLC_ELEM_FEA_FRAME_EN)) { // NOTE: If the region is already too small to shrink (eg. w=1 or h=1) // then a zero dimension box will be returned by ExpandRect() and not drawn rElemInner = gslc_ExpandRect(rElemInner,-1,-1); } // - This also changes the fill color if selected and glow state is enabled if (pElem->nFeatures & GSLC_ELEM_FEA_FILL_EN) { if (bGlowEn && bGlowing) { colBg = pElem->colElemFillGlow; } else { colBg = pElem->colElemFill; } if (pElem->nFeatures & GSLC_ELEM_FEA_ROUND_EN) { gslc_DrawFillRoundRect(pGui, rElemInner, pGui->nRoundRadius, colBg); } else { gslc_DrawFillRect(pGui, rElemInner, colBg); } } else { // TODO: If unfilled, then we might need // to redraw the background layer(s) } // -------------------------------------------------------------------------- // Frame // -------------------------------------------------------------------------- // Frame the region #ifdef DBG_FRAME // For debug purposes, draw a frame around every element gslc_DrawFrameRect(pGui,pElem->rElem,GSLC_COL_GRAY_DK1); #else if (pElem->nFeatures & GSLC_ELEM_FEA_FRAME_EN) { if (pElem->nFeatures & GSLC_ELEM_FEA_ROUND_EN) { gslc_DrawFrameRoundRect(pGui, pElem->rElem, pGui->nRoundRadius, pElem->colElemFrame); } else { gslc_DrawFrameRect(pGui, pElem->rElem, pElem->colElemFrame); } } #endif // -------------------------------------------------------------------------- // Handle special element types // -------------------------------------------------------------------------- if (pElem->nType == GSLC_TYPE_LINE) { gslc_DrawLine(pGui,nElemX,nElemY,nElemX+nElemW-1,nElemY+nElemH-1,pElem->colElemFill); } // -------------------------------------------------------------------------- // Image overlays // -------------------------------------------------------------------------- // Draw any images associated with element if (pElem->sImgRefNorm.eImgFlags != GSLC_IMGREF_NONE) { if ((bGlowEn && bGlowing) && (pElem->sImgRefGlow.eImgFlags != GSLC_IMGREF_NONE)) { gslc_DrvDrawImage(pGui,nElemX,nElemY,pElem->sImgRefGlow); } else { gslc_DrvDrawImage(pGui,nElemX,nElemY,pElem->sImgRefNorm); } } // -------------------------------------------------------------------------- // Text overlays // -------------------------------------------------------------------------- // Draw text string if defined if (pElem->pStrBuf) { gslc_tsColor colTxt = (bGlowNow)? pElem->colElemTextGlow : pElem->colElemText; int8_t nMarginX = pElem->nTxtMarginX; int8_t nMarginY = pElem->nTxtMarginY; gslc_DrawTxtBase(pGui, pElem->pStrBuf, pElem->rElem, pElem->pTxtFont, pElem->eTxtFlags, pElem->eTxtAlign, colTxt, colBg, nMarginX, nMarginY); } // -------------------------------------------------------------------------- // Mark the element as no longer requiring redraw gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_NONE); return true; } // ------------------------------------------------------------------------ // Element Update Functions // ------------------------------------------------------------------------ void gslc_ElemSetFillEn(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bFillEn) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; if (bFillEn) { pElem->nFeatures |= GSLC_ELEM_FEA_FILL_EN; } else { pElem->nFeatures &= ~GSLC_ELEM_FEA_FILL_EN; } gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } void gslc_ElemSetFrameEn(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bFrameEn) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; if (bFrameEn) { pElem->nFeatures |= GSLC_ELEM_FEA_FRAME_EN; } else { pElem->nFeatures &= ~GSLC_ELEM_FEA_FRAME_EN; } gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } void gslc_ElemSetRoundEn(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bRoundEn) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; if (bRoundEn) { pElem->nFeatures |= GSLC_ELEM_FEA_ROUND_EN; } else { pElem->nFeatures &= ~GSLC_ELEM_FEA_ROUND_EN; } gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } void gslc_ElemSetCol(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_tsColor colFrame,gslc_tsColor colFill,gslc_tsColor colFillGlow) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; if (!gslc_ColorEqual(pElem->colElemFrame, colFrame) || !gslc_ColorEqual(pElem->colElemFill, colFill) || !gslc_ColorEqual(pElem->colElemFillGlow, colFillGlow)) { pElem->colElemFrame = colFrame; pElem->colElemFill = colFill; pElem->colElemFillGlow = colFillGlow; gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } } void gslc_ElemSetGlowCol(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_tsColor colFrameGlow,gslc_tsColor colFillGlow,gslc_tsColor colTxtGlow) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; if (!gslc_ColorEqual(pElem->colElemFrameGlow, colFrameGlow) || !gslc_ColorEqual(pElem->colElemFillGlow, colFillGlow) || !gslc_ColorEqual(pElem->colElemTextGlow, colTxtGlow)) { pElem->colElemFrameGlow = colFrameGlow; pElem->colElemFillGlow = colFillGlow; pElem->colElemTextGlow = colTxtGlow; gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } } void gslc_ElemSetGroup(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,int nGroupId) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; pElem->nGroup = nGroupId; } int gslc_ElemGetGroup(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return GSLC_GROUP_ID_NONE; return pElem->nGroup; } void gslc_ElemSetRect(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_tsRect rElem) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; // Invalidate region including both rects from before & after gslc_InvalidateRgnAdd(pGui, pElem->rElem); // Old region gslc_InvalidateRgnAdd(pGui, rElem); // New region // Force a page redraw within the scope defined by the invalidation region gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); gslc_PageRedrawSet(pGui,true); // Update element pElem->rElem = rElem; gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } gslc_tsRect gslc_ElemGetRect(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return (gslc_tsRect){0,0,0,0}; return pElem->rElem; } void gslc_ElemSetTxtAlign(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,unsigned nAlign) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; pElem->eTxtAlign = nAlign; gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } void gslc_ElemSetTxtMargin(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,unsigned nMargin) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; pElem->nTxtMarginX = nMargin; pElem->nTxtMarginY = nMargin; gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } void gslc_ElemSetTxtMarginXY(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,int8_t nMarginX,int8_t nMarginY) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; pElem->nTxtMarginX = nMarginX; pElem->nTxtMarginY = nMarginY; gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } void gslc_ElemSetTxtStr(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,const char* pStr) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; // Check for read-only status (in case the string was // defined in Flash/PROGMEM) if (pElem->nStrBufMax == 0) { // String was read-only, so abort now return; } // To avoid unnecessary redraw / flicker, only a change in // the text content will drive a redraw if (strncmp(pElem->pStrBuf,pStr,pElem->nStrBufMax-1)) { strncpy(pElem->pStrBuf,pStr,pElem->nStrBufMax-1); pElem->pStrBuf[pElem->nStrBufMax-1] = '\0'; // Force termination gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_INC); } } char* gslc_ElemGetTxtStr(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return NULL; return pElem->pStrBuf; } void gslc_ElemSetTxtCol(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_tsColor colVal) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; if (!gslc_ColorEqual(pElem->colElemText, colVal) || !gslc_ColorEqual(pElem->colElemTextGlow, colVal)) { pElem->colElemText = colVal; pElem->colElemTextGlow = colVal; // Default to same color for glowing state // TODO: Might want to change to GSLC_REDRAW_INC gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } } void gslc_ElemSetTxtMem(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_teTxtFlags eFlags) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; if (GSLC_LOCAL_STR) { if ((eFlags & GSLC_TXT_MEM) == GSLC_TXT_MEM_PROG) { // ERROR: Unsupported mode // - We don't support internal buffer mode with initialization // from flash (PROGMEM) GSLC_DEBUG2_PRINT("ERROR: ElemSetTxtMem(%s) GSLC_LOCAL_STR can't be used with GSLC_TXT_MEM_PROG\n",""); return; } } gslc_teTxtFlags eFlagsCur = pElem->eTxtFlags; pElem->eTxtFlags = (eFlagsCur & ~GSLC_TXT_MEM) | (eFlags & GSLC_TXT_MEM); } void gslc_ElemSetTxtEnc(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_teTxtFlags eFlags) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; gslc_teTxtFlags eFlagsCur = pElem->eTxtFlags; pElem->eTxtFlags = (eFlagsCur & ~GSLC_TXT_ENC) | (eFlags & GSLC_TXT_ENC); } void gslc_ElemUpdateFont(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,int nFontId) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; pElem->pTxtFont = gslc_FontGet(pGui,nFontId); gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } void gslc_ElemSetRedraw(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_teRedrawType eRedraw) { if (pElemRef == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemSetRedraw"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } // Update the redraw flag gslc_teElemRefFlags eFlags = pElemRef->eElemFlags; gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); switch (eRedraw) { case GSLC_REDRAW_NONE: eFlags = (eFlags & ~GSLC_ELEMREF_REDRAW_MASK) | GSLC_ELEMREF_REDRAW_NONE; break; case GSLC_REDRAW_FULL: eFlags = (eFlags & ~GSLC_ELEMREF_REDRAW_MASK) | GSLC_ELEMREF_REDRAW_FULL; // Mark the region as invalidated // - Only invalidate if the element is visible on the screen if (gslc_ElemGetOnScreen(pGui,pElemRef)) { gslc_InvalidateRgnAdd(pGui, pElem->rElem); } break; case GSLC_REDRAW_INC: eFlags = (eFlags & ~GSLC_ELEMREF_REDRAW_MASK) | GSLC_ELEMREF_REDRAW_INC; // Mark the region as invalidated // - Only invalidate if the element is visible on the screen if (gslc_ElemGetOnScreen(pGui,pElemRef)) { gslc_InvalidateRgnAdd(pGui, pElem->rElem); } break; } // Handle request for update of redraw state // - We permit the new state to be assigned except in the // case wherein we are currently pending a full redraw // but an incremental redraw has been requested. In that // case we leave the full redraw request pending. if (eRedraw == GSLC_REDRAW_INC) { if ((eFlags & GSLC_ELEMREF_REDRAW_MASK) == GSLC_ELEMREF_REDRAW_FULL) { // Don't update redraw state; leave it as full redraw pending } else { pElemRef->eElemFlags = eFlags; } } else { pElemRef->eElemFlags = eFlags; } #if (GSLC_FEATURE_COMPOUND) // In the case of compound elements, optionally propagate the // redraw status up the hierarchy (ie. to the parent element). // - This may be useful in scenarios wherein a sub-element has // been marked for redraw, but because it has transparency, // the parent element may need to be redrawn first. // - For now, assume no need to trigger a parent redraw. // - TODO: Consider detecting scenarios in which we should // propagate the redraw to the parent. if (pElem->pElemRefParent != NULL) { gslc_ElemSetRedraw(pGui,pElem->pElemRefParent,eRedraw); } #endif } gslc_teRedrawType gslc_ElemGetRedraw(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { if (pElemRef == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemGetRedraw"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return GSLC_REDRAW_NONE; } gslc_teElemRefFlags eFlags = pElemRef->eElemFlags; switch (eFlags & GSLC_ELEMREF_REDRAW_MASK) { case GSLC_ELEMREF_REDRAW_NONE: return GSLC_REDRAW_NONE; case GSLC_ELEMREF_REDRAW_FULL: return GSLC_REDRAW_FULL; case GSLC_ELEMREF_REDRAW_INC: return GSLC_REDRAW_INC; } // Should not reach here return GSLC_REDRAW_NONE; } void gslc_ElemSetGlow(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bGlowing) { if (pElemRef == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemSetGlow"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } // Only change glow state if enabled if (gslc_ElemGetGlowEn(pGui, pElemRef)) { bool bGlowingOld = gslc_ElemGetGlow(pGui, pElemRef); gslc_SetElemRefFlag(pGui, pElemRef, GSLC_ELEMREF_GLOWING, (bGlowing) ? GSLC_ELEMREF_GLOWING : 0); if (bGlowing != bGlowingOld) { gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_INC); } } } bool gslc_ElemGetGlow(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { if (pElemRef == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemGetGlow"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return false; } return gslc_GetElemRefFlag(pGui,pElemRef,GSLC_ELEMREF_GLOWING); } void gslc_ElemSetVisible(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bVisible) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; bool bVisibleOld = gslc_ElemGetVisible(pGui,pElemRef); gslc_SetElemRefFlag(pGui,pElemRef,GSLC_ELEMREF_VISIBLE,(bVisible)?GSLC_ELEMREF_VISIBLE:0); // Mark the element as needing redraw if its visibility status changed if (bVisible != bVisibleOld) { gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); if (bVisible == false) { // Since we are hiding an element, we need to invalidate // the region underneath the element, so that it can // be redrawn. gslc_InvalidateRgnAdd(pGui, pElem->rElem); // Mark the page as having a redraw pending gslc_PageRedrawSet(pGui,true); } } } bool gslc_ElemGetVisible(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { if (pElemRef == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemGetVisible"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return false; } return gslc_GetElemRefFlag(pGui,pElemRef,GSLC_ELEMREF_VISIBLE); } bool gslc_ElemGetOnScreen(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef) { int16_t nElemId = gslc_ElemGetId(pGui, pElemRef); for (int8_t nStackPage = 0; nStackPage < GSLC_STACK__MAX; nStackPage++) { gslc_tsPage* pStackPage = pGui->apPageStack[nStackPage]; if (!pStackPage) { continue; } gslc_tsCollect* pCollect = &pStackPage->sCollect; pElemRef = gslc_CollectFindElemById(pGui, pCollect, nElemId); if (pElemRef) { return gslc_ElemGetVisible(pGui, pElemRef); } } return false; } void gslc_ElemSetGlowEn(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bGlowEn) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; if (bGlowEn) { pElem->nFeatures |= GSLC_ELEM_FEA_GLOW_EN; } else { pElem->nFeatures &= ~GSLC_ELEM_FEA_GLOW_EN; } gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_INC); } bool gslc_ElemGetGlowEn(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return false; return pElem->nFeatures & GSLC_ELEM_FEA_GLOW_EN; } void gslc_ElemSetClickEn(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bClickEn) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; if (bClickEn) { pElem->nFeatures |= GSLC_ELEM_FEA_CLICK_EN; } else { pElem->nFeatures &= ~GSLC_ELEM_FEA_CLICK_EN; } // No need to call ElemSetRedraw() as we aren't changing a visual characteristic } void gslc_ElemSetTouchFunc(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, GSLC_CB_TOUCH funcCb) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; pElem->pfuncXTouch = funcCb; } void gslc_ElemSetStyleFrom(gslc_tsGui* pGui,gslc_tsElemRef* pElemRefSrc,gslc_tsElemRef* pElemRefDest) { gslc_tsElem* pElemSrc = gslc_GetElemFromRefD(pGui, pElemRefSrc, __LINE__); gslc_tsElem* pElemDest = gslc_GetElemFromRefD(pGui, pElemRefDest, __LINE__); if ((!pElemSrc) || (!pElemDest)) return; // TODO: Check ElemRef SRC type for compatibility // nId // nType // rElem pElemDest->nGroup = pElemSrc->nGroup; pElemDest->nFeatures = pElemSrc->nFeatures; pElemDest->sImgRefNorm = pElemSrc->sImgRefNorm; pElemDest->sImgRefGlow = pElemSrc->sImgRefGlow; pElemDest->colElemFill = pElemSrc->colElemFill; pElemDest->colElemFillGlow = pElemSrc->colElemFillGlow; pElemDest->colElemFrame = pElemSrc->colElemFrame; pElemDest->colElemFrameGlow = pElemSrc->colElemFrameGlow; // eRedraw #ifdef GLSC_COMPOUND pElemDest->pElemRefParent = pElemSrc->pElemRefParent; #endif // Don't copy over the text strings // pStrBuf[GSLC_LOCAL_STR_LEN] // pStr // nStrMax // eTxtFlags pElemDest->colElemText = pElemSrc->colElemText; pElemDest->colElemTextGlow = pElemSrc->colElemTextGlow; pElemDest->eTxtAlign = pElemSrc->eTxtAlign; pElemDest->nTxtMarginX = pElemSrc->nTxtMarginX; pElemDest->nTxtMarginY = pElemSrc->nTxtMarginY; pElemDest->pTxtFont = pElemSrc->pTxtFont; // pXData //pElemDest->pfuncXEvent = pElemSrc->pfuncXEvent; // UNUSED pElemDest->pfuncXDraw = pElemSrc->pfuncXDraw; pElemDest->pfuncXTouch = pElemSrc->pfuncXTouch; pElemDest->pfuncXTick = pElemSrc->pfuncXTick; gslc_ElemSetRedraw(pGui,pElemRefDest,GSLC_REDRAW_FULL); } /* UNUSED void gslc_ElemSetEventFunc(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,GSLC_CB_EVENT funcCb) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; pElem->pfuncXEvent = funcCb; } */ void gslc_ElemSetDrawFunc(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,GSLC_CB_DRAW funcCb) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; pElem->pfuncXDraw = funcCb; gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); } void gslc_ElemSetTickFunc(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,GSLC_CB_TICK funcCb) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; pElem->pfuncXTick = funcCb; } bool gslc_ElemOwnsCoord(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,int16_t nX,int16_t nY,bool bOnlyClickEn) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return false; if (bOnlyClickEn && !(pElem->nFeatures & GSLC_ELEM_FEA_CLICK_EN) ) { return false; } return gslc_IsInRect(nX,nY,pElem->rElem); } #if !defined(DRV_TOUCH_NONE) // ------------------------------------------------------------------------ // Tracking Functions // ------------------------------------------------------------------------ void gslc_CollectInput(gslc_tsGui* pGui,gslc_tsCollect* pCollect,gslc_tsEventTouch* pEventTouch) { #if !(GSLC_FEATURE_INPUT) return; #else // Fetch the data members of the touch event gslc_teTouch eTouch = pEventTouch->eTouch; int16_t nInputInd = pEventTouch->nX; // nX overloaded as element index int16_t nInputVal = pEventTouch->nY; // nY overloaded as action value gslc_tsElemRef* pElemIndRef; if ((nInputInd >= 0) && (nInputInd < pCollect->nElemRefMax)) { pElemIndRef = &(pCollect->asElemRef[nInputInd]); } else { pElemIndRef = NULL; } gslc_tsElemRef* pTrackedRefOld = NULL; gslc_tsElemRef* pTrackedRefNew = NULL; // Pass zero to callbacks since no actual position int16_t nX = 0; int16_t nY = 0; // Fetch the item currently being tracked (if any) pTrackedRefOld = gslc_CollectGetElemRefTracked(pGui,pCollect); // Reset the in-tracked flag bool bInTracked = false; // For direct input events, eTouch will already be fully defined with DOWN/UP + IN/OUT if (eTouch == GSLC_TOUCH_FOCUS_ON) { // --------------------------------- // Touch Down Event // --------------------------------- // End glow on previously tracked element (if any) // - We shouldn't really enter a "Touch Down" event // with an element still marked as being tracked if (pTrackedRefOld != NULL) { gslc_ElemSetGlow(pGui,pTrackedRefOld,false); } // Determine the new element to start tracking pTrackedRefNew = pElemIndRef; if (pTrackedRefNew == NULL) { // Didn't find an element, so clear the tracking reference gslc_CollectSetElemTracked(pGui,pCollect,NULL); } else { // Found an element, so mark it as being the tracked element // Set the new tracked element reference gslc_CollectSetElemTracked(pGui,pCollect,pTrackedRefNew); if (pTrackedRefNew->pElem == NULL) { return; } // Start glow on new element gslc_ElemSetGlow(pGui,pTrackedRefNew,true); // Notify element for optional custom handling // - We do this after we have determined which element should // receive the touch tracking eTouch = GSLC_TOUCH_DOWN_IN; //gslc_ElemSendEventTouch(pGui,pTrackedRefNew,eTouch,nX,nY); } } else if ((eTouch == GSLC_TOUCH_FOCUS_OFF) || (eTouch == GSLC_TOUCH_FOCUS_SELECT)) { // --------------------------------- // Touch Up Event // --------------------------------- if (pTrackedRefOld != NULL) { // Are we still over tracked element? if (eTouch == GSLC_TOUCH_FOCUS_OFF) { bInTracked = false; } else if (eTouch == GSLC_TOUCH_FOCUS_SELECT) { bInTracked = true; } if (!bInTracked) { // Released not over tracked element eTouch = GSLC_TOUCH_UP_OUT; //gslc_ElemSendEventTouch(pGui,pTrackedRefOld,eTouch,nX,nY); } else { // Notify original tracked element for optional custom handling eTouch = GSLC_TOUCH_UP_IN; // FIXME: Do we really want to change the eTouch type here? // Perhaps this is the best way to reuse the existing touch handler in the element nX = 0; // Arbitrary nY = 0; // Arbitrary gslc_ElemSendEventTouch(pGui,pTrackedRefOld,eTouch,nX,nY); } // Clear glow state if (pTrackedRefOld->pElem == NULL) { return; } gslc_ElemSetGlow(pGui,pTrackedRefOld,false); } // Clear the element tracking state gslc_CollectSetElemTracked(pGui,pCollect,NULL); } else if ((eTouch == GSLC_TOUCH_SET_REL) || (eTouch == GSLC_TOUCH_SET_ABS)) { // --------------------------------- // Element Value Adjust Event // --------------------------------- nX = 0; // Unused nY = nInputVal; // Relative or Absolute value gslc_ElemSendEventTouch(pGui,pTrackedRefOld,eTouch,nX,nY); } #endif // GSLC_FEATURE_INPUT } void gslc_CollectTouch(gslc_tsGui* pGui,gslc_tsCollect* pCollect,gslc_tsEventTouch* pEventTouch) { // Fetch the data members of the touch event int16_t nX = pEventTouch->nX; int16_t nY = pEventTouch->nY; gslc_teTouch eTouch = pEventTouch->eTouch; gslc_tsElemRef* pTrackedRefOld = NULL; gslc_tsElemRef* pTrackedRefNew = NULL; // Fetch the item currently being tracked (if any) pTrackedRefOld = gslc_CollectGetElemRefTracked(pGui,pCollect); // Reset the in-tracked flag bool bInTracked = false; if (eTouch == GSLC_TOUCH_DOWN) { // --------------------------------- // Touch Down Event // --------------------------------- // End glow on previously tracked element (if any) // - We shouldn't really enter a "Touch Down" event // with an element still marked as being tracked if (pTrackedRefOld != NULL) { gslc_ElemSetGlow(pGui,pTrackedRefOld,false); } // Determine the new element to start tracking pTrackedRefNew = gslc_CollectFindElemFromCoord(pGui,pCollect,nX,nY); if (pTrackedRefNew == NULL) { // Didn't find an element, so clear the tracking reference gslc_CollectSetElemTracked(pGui,pCollect,NULL); } else { // Found an element, so mark it as being the tracked element // Set the new tracked element reference gslc_CollectSetElemTracked(pGui,pCollect,pTrackedRefNew); // Start glow on new element gslc_ElemSetGlow(pGui,pTrackedRefNew,true); // Notify element for optional custom handling // - We do this after we have determined which element should // receive the touch tracking eTouch = GSLC_TOUCH_DOWN_IN; gslc_ElemSendEventTouch(pGui,pTrackedRefNew,eTouch,nX,nY); } } else if (eTouch == GSLC_TOUCH_UP) { // --------------------------------- // Touch Up Event // --------------------------------- if (pTrackedRefOld != NULL) { // Are we still over tracked element? bInTracked = gslc_ElemOwnsCoord(pGui,pTrackedRefOld,nX,nY,true); if (!bInTracked) { // Released not over tracked element eTouch = GSLC_TOUCH_UP_OUT; gslc_ElemSendEventTouch(pGui,pTrackedRefOld,eTouch,nX,nY); } else { // Notify original tracked element for optional custom handling eTouch = GSLC_TOUCH_UP_IN; gslc_ElemSendEventTouch(pGui,pTrackedRefOld,eTouch,nX,nY); } // Clear glow state gslc_ElemSetGlow(pGui,pTrackedRefOld,false); } // Clear the element tracking state gslc_CollectSetElemTracked(pGui,pCollect,NULL); } else if (eTouch == GSLC_TOUCH_MOVE) { // --------------------------------- // Touch Move Event // --------------------------------- // Determine if we are still over tracked element if (pTrackedRefOld != NULL) { bInTracked = gslc_ElemOwnsCoord(pGui,pTrackedRefOld,nX,nY,true); if (!bInTracked) { // Not currently over tracked element // - Notify tracked element that we moved out of it eTouch = GSLC_TOUCH_MOVE_OUT; gslc_ElemSendEventTouch(pGui,pTrackedRefOld,eTouch,nX,nY); // Ensure the tracked element is no longer glowing if (pTrackedRefOld) { gslc_ElemSetGlow(pGui,pTrackedRefOld,false); } } else { // We are still over tracked element // - Notify tracked element eTouch = GSLC_TOUCH_MOVE_IN; gslc_ElemSendEventTouch(pGui,pTrackedRefOld,eTouch,nX,nY); // Ensure it is glowing gslc_ElemSetGlow(pGui,pTrackedRefOld,true); } } } } #if (GSLC_FEATURE_COMPOUND) // Helper function for dispatching touch events to sub-components bool gslc_CollectTouchCompound(void* pvGui, void* pvElemRef, gslc_teTouch eTouch, int16_t nRelX, int16_t nRelY, gslc_tsCollect* pCollect) { #if defined(DRV_TOUCH_NONE) return false; #else gslc_tsGui* pGui = (gslc_tsGui*)(pvGui); gslc_tsElemRef* pElemRef = (gslc_tsElemRef*)(pvElemRef); gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return false; // Reset the in/out status of the touch event since // we are now looking for coordinates of a sub-element // rather than the compound element. gslc_CollectTouch() // is responsible for determining in/out status at the // next level down in the element hierarchy. switch (eTouch) { case GSLC_TOUCH_DOWN_IN: case GSLC_TOUCH_DOWN_OUT: eTouch &= ~GSLC_TOUCH_SUBTYPE_MASK; eTouch |= GSLC_TOUCH_DOWN; break; case GSLC_TOUCH_UP_IN: case GSLC_TOUCH_UP_OUT: eTouch &= ~GSLC_TOUCH_SUBTYPE_MASK; eTouch |= GSLC_TOUCH_UP; break; case GSLC_TOUCH_MOVE_IN: case GSLC_TOUCH_MOVE_OUT: eTouch &= ~GSLC_TOUCH_SUBTYPE_MASK; eTouch |= GSLC_TOUCH_MOVE; break; default: break; } // Cascade the touch event to the sub-element collection // - Note that we convert to absolute coordinates gslc_tsEventTouch sEventTouch; sEventTouch.nX = pElem->rElem.x + nRelX; sEventTouch.nY = pElem->rElem.y + nRelY; sEventTouch.eTouch = eTouch; gslc_CollectTouch(pGui, pCollect, &sEventTouch); return true; #endif // !DRV_TOUCH_NONE } #endif // GSLC_FEATURE_COMPOUND // FIXME: pPage UNUSED with new PageStack implementation void gslc_TrackInput(gslc_tsGui* pGui,gslc_tsPage* pPage,gslc_teInputRawEvent eInputEvent,int16_t nInputVal) { #if !(GSLC_FEATURE_INPUT) GSLC_DEBUG_PRINT("WARNING: GSLC_FEATURE_INPUT not enabled in GUIslice config%s\n", ""); return; #else if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "TrackInput"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } gslc_tsEventTouch sEventTouch; void* pvData = (void*)(&sEventTouch); gslc_tsEvent sEvent; int16_t nFocusInd = GSLC_IND_NONE; gslc_tsPage* pFocusPage = NULL; gslc_tsCollect* pCollect = NULL; gslc_teAction eAction = GSLC_ACTION_NONE; int16_t nActionVal; // Determine the page to accept focus // - For now, use the top enabled page in the stack for (int nStack = 0; nStack < GSLC_STACK__MAX; nStack++) { if (pGui->apPageStack[nStack]) { // Ensure the page layer is active (receiving touch events) if (pGui->abPageStackActive[nStack]) { pFocusPage = pGui->apPageStack[nStack]; } } } if (!pFocusPage) { // No pages enabled in the stack, so exit return; } gslc_InputMapLookup(pGui,eInputEvent,nInputVal,&eAction,&nActionVal); switch(eAction) { case GSLC_ACTION_FOCUS_PREV: case GSLC_ACTION_FOCUS_NEXT: // Unfocus old element sEventTouch.eTouch = GSLC_TOUCH_FOCUS_OFF; sEventTouch.nX = GSLC_IND_NONE; sEventTouch.nY = 0; // Unused sEvent = gslc_EventCreate(pGui,GSLC_EVT_TOUCH,0,(void*)pFocusPage,pvData); gslc_PageEvent(pGui,sEvent); // Focus on new element bool bStepNext = (eAction == GSLC_ACTION_FOCUS_NEXT); nFocusInd = gslc_PageFocusStep(pGui,pFocusPage,bStepNext); if (nFocusInd == GSLC_IND_NONE) { break; } sEventTouch.eTouch = GSLC_TOUCH_FOCUS_ON; sEventTouch.nX = nFocusInd; sEventTouch.nY = 0; // Unused sEvent = gslc_EventCreate(pGui,GSLC_EVT_TOUCH,0,(void*)pFocusPage,pvData); gslc_PageEvent(pGui,sEvent); break; case GSLC_ACTION_SELECT: pCollect = &pFocusPage->sCollect; nFocusInd = gslc_CollectGetFocus(pGui, pCollect); // Ensure an element is in focus! if (nFocusInd == GSLC_IND_NONE) { break; } else { // Select currently focused element sEventTouch.eTouch = GSLC_TOUCH_FOCUS_SELECT; sEventTouch.nX = nFocusInd; sEventTouch.nY = 0; // Unused sEvent = gslc_EventCreate(pGui,GSLC_EVT_TOUCH,0,(void*)pFocusPage,pvData); gslc_PageEvent(pGui,sEvent); // Reapply focus to current element sEventTouch.eTouch = GSLC_TOUCH_FOCUS_ON; sEvent = gslc_EventCreate(pGui,GSLC_EVT_TOUCH,0,(void*)pFocusPage,pvData); gslc_PageEvent(pGui,sEvent); } break; case GSLC_ACTION_SET_REL: case GSLC_ACTION_SET_ABS: pCollect = &pFocusPage->sCollect; nFocusInd = gslc_CollectGetFocus(pGui, pCollect); // Ensure an element is in focus! if (nFocusInd == GSLC_IND_NONE) { break; } else { // Change current element if (eAction == GSLC_ACTION_SET_REL) { sEventTouch.eTouch = GSLC_TOUCH_SET_REL; sEventTouch.nX = 0; // Unused sEventTouch.nY = nActionVal; } else { sEventTouch.eTouch = GSLC_TOUCH_SET_ABS; sEventTouch.nX = 0; // Unused sEventTouch.nY = nActionVal; } sEvent = gslc_EventCreate(pGui,GSLC_EVT_TOUCH,0,(void*)pFocusPage,pvData); gslc_PageEvent(pGui,sEvent); } break; case GSLC_ACTION_UNDEF: // ERROR: unknown action break; default: // No action mapped break; } #endif // GSLC_FEATURE_INPUT } // This routine is responsible for the GUI-level touch event state machine // and dispatching to the touch event handler for the page // FIXME: pPage UNUSED with new PageStack implementation void gslc_TrackTouch(gslc_tsGui* pGui,gslc_tsPage* pPage,int16_t nX,int16_t nY,uint16_t nPress) { if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "TrackTouch"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } // Determine the transitions in the touch events based // on the previous touch pressure state // TODO: Provide hysteresis thresholds for the touch // pressure levels in case a display outputs a // variable range (eg. 15..200) that doesn't // go to zero when touch is removed. gslc_teTouch eTouch = GSLC_TOUCH_NONE; if ((pGui->nTouchLastPress == 0) && (nPress > 0)) { eTouch = GSLC_TOUCH_DOWN; #ifdef DBG_TOUCH GSLC_DEBUG_PRINT("Trk: (%3d,%3d) P=%3u : TouchDown\n\n",nX,nY,nPress); #endif } else if ((pGui->nTouchLastPress > 0) && (nPress == 0)) { eTouch = GSLC_TOUCH_UP; #ifdef DBG_TOUCH GSLC_DEBUG_PRINT("Trk: (%3d,%3d) P=%3u : TouchUp\n\n",nX,nY,nPress); #endif } else if ((pGui->nTouchLastX != nX) || (pGui->nTouchLastY != nY)) { // We only track movement if touch is "down" if (nPress > 0) { eTouch = GSLC_TOUCH_MOVE; #ifdef DBG_TOUCH GSLC_DEBUG_PRINT("Trk: (%3d,%3d) P=%3u : TouchMove\n\n",nX,nY,nPress); #endif } } gslc_tsEventTouch sEventTouch; sEventTouch.eTouch = eTouch; // Save the coordinates from the touch driver // NOTE: Many display touch drivers return valid coordinates upon a // TOUCH_UP event, whereas some return zero position coordinates in // this event (eg. TFT_eSPI with ILI9486). Thus, to ensure that we // have consistent detection of position when the touch is released, // we will pass the previous good position in this event (transition // from TOUCH_DOWN to TOUCH_UP). // // The position during the TOUCH_UP event is used to determine if a // touch was released within an element (causing a button selection) // or outside of it (generally leading to a non-selection). // if (eTouch == GSLC_TOUCH_UP) { // Use previous (good) coordinates from touch driver sEventTouch.nX = pGui->nTouchLastX; sEventTouch.nY = pGui->nTouchLastY; } else { // Use most recent coordinate from touch driver sEventTouch.nX = nX; sEventTouch.nY = nY; } void* pvData = (void*)(&sEventTouch); gslc_tsEvent sEvent; // Generate touch page event for any enabled pages in the stack for (unsigned nStack = 0; nStack < GSLC_STACK__MAX; nStack++) { gslc_tsPage* pStackPage = pGui->apPageStack[nStack]; if (pStackPage) { // Ensure the page layer is active (receiving touch events) if (pGui->abPageStackActive[nStack]) { sEvent = gslc_EventCreate(pGui, GSLC_EVT_TOUCH, 0, (void*)pStackPage, pvData); gslc_PageEvent(pGui, sEvent); } } } // Save raw touch status so that we can detect transitions pGui->nTouchLastX = nX; pGui->nTouchLastY = nY; pGui->nTouchLastPress = nPress; } // ------------------------------------------------------------------------ // Touchscreen Functions // ------------------------------------------------------------------------ bool gslc_InitTouch(gslc_tsGui* pGui,const char* acDev) { bool bOk; if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "InitTouch"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return false; } // Call driver-specific touchscreen init // // Determine if touch events are provided by the display driver // or an external touch driver. #if defined(DRV_TOUCH_NONE) // Touch handling disabled bOk = true; #elif defined(DRV_TOUCH_IN_DISP) // Touch handling by display driver bOk = gslc_DrvInitTouch(pGui,acDev); #else // Touch handling by external touch driver bOk = gslc_TDrvInitTouch(pGui,acDev); #endif if (!bOk) { GSLC_DEBUG2_PRINT("ERROR: InitTouch() failed in touch driver init\n",0); } return bOk; } bool gslc_GetTouch(gslc_tsGui* pGui,int16_t* pnX,int16_t* pnY,uint16_t* pnPress,gslc_teInputRawEvent* peInputEvent,int16_t* pnInputVal) { if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "GetTouch"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return false; } *peInputEvent = GSLC_INPUT_NONE; *pnInputVal = 0; #if defined(DRV_TOUCH_NONE) // Touch handling disabled return false; #elif defined(DRV_TOUCH_IN_DISP) // Use display driver for touch events return gslc_DrvGetTouch(pGui,pnX,pnY,pnPress,peInputEvent,pnInputVal); #else // Use external touch driver for touch events return gslc_TDrvGetTouch(pGui,pnX,pnY,pnPress,peInputEvent,pnInputVal); #endif return false; } void gslc_SetTouchRemapEn(gslc_tsGui* pGui, bool bEn) { if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "SetTouchRemapEn"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL, FUNCSTR); return; } pGui->bTouchRemapEn = bEn; } void gslc_SetTouchRemapCal(gslc_tsGui* pGui,uint16_t nXMin, uint16_t nXMax, uint16_t nYMin, uint16_t nYMax) { if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "SetTouchRemapCal"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL, FUNCSTR); return; } pGui->nTouchCalXMin = nXMin; pGui->nTouchCalXMax = nXMax; pGui->nTouchCalYMin = nYMin; pGui->nTouchCalYMax = nYMax; } void gslc_SetTouchRemapYX(gslc_tsGui* pGui, bool bSwap) { if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "SetTouchRemapYX"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL, FUNCSTR); return; } pGui->bTouchRemapYX = bSwap; } #endif // !DRV_TOUCH_NONE // ------------------------------------------------------------------------ // Private Functions // ------------------------------------------------------------------------ // NOTE: nId is a positive ID specified by the user or // GSLC_ID_AUTO if the user wants an auto-generated ID // (which will be assigned in Element nId) #if (GSLC_FEATURE_COMPOUND) // NOTE: When we are creating sub-elements within a compound element, // we usually pass nPageId=GSLC_PAGE_NONE. In this mode we // won't add the element to any page, but just create the // element struct. However, in this mode we can't support // auto-generated IDs since we don't know which IDs will // be taken when we finally create the compound element. #endif gslc_tsElem gslc_ElemCreate(gslc_tsGui* pGui,int16_t nElemId,int16_t nPageId, int16_t nType,gslc_tsRect rElem,char* pStrBuf,uint8_t nStrBufMax,int16_t nFontId) { gslc_tsElem sElem; // Assign defaults to the element record gslc_ResetElem(&sElem); if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemCreate"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return sElem; } gslc_tsPage* pPage = NULL; gslc_tsCollect* pCollect = NULL; // If we are going to be adding the element to a page then we // perform some additional checks if (nPageId == GSLC_PAGE_NONE) { #if (GSLC_FEATURE_COMPOUND) // This is a temporary element, so we skip the ID collision checks. // In this mode we don't support auto ID assignment if (nElemId == GSLC_ID_AUTO) { GSLC_DEBUG2_PRINT("ERROR: ElemCreate(%s) doesn't support temp elements with auto ID\n",""); return sElem; } #endif } else { // Look up the targeted page to ensure that we check its // collection for collision with other IDs (or assign the // next available if auto-incremented) pPage = gslc_PageFindById(pGui,nPageId); if (pPage == NULL) { GSLC_DEBUG2_PRINT("ERROR: ElemCreate() can't find page (ID=%d)\n",nPageId); return sElem; } pCollect = &pPage->sCollect; // Validate the user-supplied ID if (nElemId == GSLC_ID_AUTO) { // Get next auto-generated ID nElemId = gslc_CollectGetNextId(pGui,pCollect); } else { // Ensure the ID is positive if (nElemId < 0) { GSLC_DEBUG2_PRINT("ERROR: ElemCreate() called with negative ID (%d)\n",nElemId); return sElem; } // Ensure the ID isn't already taken if (gslc_CollectFindElemById(pGui,pCollect,nElemId) != NULL) { GSLC_DEBUG2_PRINT("ERROR: ElemCreate() called with existing ID (%d)\n",nElemId); return sElem; } } } // Override defaults with parameterization sElem.nId = nElemId; sElem.rElem = rElem; sElem.nType = nType; sElem.pTxtFont = gslc_FontGet(pGui,nFontId); // Initialize the local string buffer (if enabled via GSLC_LOCAL_STR) // otherwise just save a copy of the external string buffer pointer // and maximum buffer length from the parameters. // // NOTE: GSLC_LOCAL_STR=1 mode does not accept string pointers to // PROGMEM (Flash). This is because at time of ElemCreate() // the string location (SRAM vs PROGMEM) won't be known (as // ElemSetTxtMem() has not been called yet). Therefore, we // wouldn't know to perform a copy from PROGMEM. This should not // be a problem since it would be unlikely that a user would // want to use internal buffers but initialized by content in // flash. if (pStrBuf == NULL) { // No string enabled, so set the flag accordingly sElem.nStrBufMax = 0; sElem.eTxtFlags = (sElem.eTxtFlags & ~GSLC_TXT_ALLOC) | GSLC_TXT_ALLOC_NONE; } else { #if (GSLC_LOCAL_STR) // NOTE: Assume the string buffer pointer is located in RAM and not PROGMEM strncpy(sElem.pStrBuf,pStrBuf,GSLC_LOCAL_STR_LEN-1); sElem.pStrBuf[GSLC_LOCAL_STR_LEN-1] = '\0'; // Force termination sElem.nStrBufMax = GSLC_LOCAL_STR_LEN; sElem.eTxtFlags = (sElem.eTxtFlags & ~GSLC_TXT_ALLOC) | GSLC_TXT_ALLOC_INT; #else // No need to copy locally; instead, we are going to retain // the external string pointer (must be static) sElem.pStrBuf = pStrBuf; sElem.nStrBufMax = nStrBufMax; sElem.eTxtFlags = (sElem.eTxtFlags & ~GSLC_TXT_ALLOC) | GSLC_TXT_ALLOC_EXT; #endif } // If the element creation was successful, then set the valid flag sElem.nFeatures |= GSLC_ELEM_FEA_VALID; return sElem; } // ------------------------------------------------------------------------ // Collect Event Handlers // ------------------------------------------------------------------------ // Common event handler bool gslc_CollectEvent(void* pvGui,gslc_tsEvent sEvent) { if (pvGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "CollectEvent"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return false; } void* pvScope = sEvent.pvScope; gslc_tsCollect* pCollect = (gslc_tsCollect*)(pvScope); unsigned nInd; gslc_tsElemRef* pElemRef = NULL; // Handle any collection-based events first // ... if (sEvent.eType == GSLC_EVT_TOUCH) { #if defined(DRV_TOUCH_NONE) return false; #else gslc_tsGui* pGui = (gslc_tsGui*)(pvGui); void* pvData = sEvent.pvData; // TOUCH is passed to CollectTouch which determines the element // in the collection that should receive the event gslc_tsEventTouch* pEventTouch = (gslc_tsEventTouch*)(pvData); gslc_teTouch eTouch = pEventTouch->eTouch; if ((eTouch & GSLC_TOUCH_TYPE_MASK) == GSLC_TOUCH_COORD) { gslc_CollectTouch(pGui,pCollect,pEventTouch); } else if ((eTouch & GSLC_TOUCH_TYPE_MASK) == GSLC_TOUCH_DIRECT) { gslc_CollectInput(pGui,pCollect,pEventTouch); } else { // FAIL } return true; #endif // !DRV_TOUCH_NONE } else if ( (sEvent.eType == GSLC_EVT_DRAW) || (sEvent.eType == GSLC_EVT_TICK) ) { // DRAW and TICK are propagated down to all elements in collection for (nInd=0;nIndnElemRefCnt;nInd++) { pElemRef = &(pCollect->asElemRef[nInd]); // Copy event so we can modify it in the loop gslc_tsEvent sEventNew = sEvent; sEventNew.pvScope = (void*)(pElemRef); // Propagate the event to the element gslc_ElemEvent(pvGui,sEventNew); } // nInd } // eType return true; } // NOTE: // - When eFlags=GSLC_ELEMREF_SRC_RAM, the contents of pElem are copied into // the internal element array so that the pointer (pElem) can be released // after this call (ie. it can be an automatic variable). // - When eFlags=GSLC_ELEMREF_SRC_PROG, the pointer value pElem is saved // into the element reference array so that the element can be fetched // directly from PROGMEM (Flash) at a later time. In this mode, pElem // must be a static variable. // - When eFlags=GSLC_ELEMREF_SRC_CONST, the same is done as for // GSLC_ELEMREF_SRC_PROG except that PROGMEM is not required to access. gslc_tsElemRef* gslc_CollectElemAdd(gslc_tsGui* pGui,gslc_tsCollect* pCollect,const gslc_tsElem* pElem,gslc_teElemRefFlags eFlags) { if ((pCollect == NULL) || (pElem == NULL)) { static const char GSLC_PMEM FUNCSTR[] = "CollectElemAdd"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return NULL; } if (pCollect->nElemRefCnt+1 > (pCollect->nElemRefMax)) { GSLC_DEBUG2_PRINT("ERROR: CollectElemAdd() too many element references (%d/%d)\n", pCollect->nElemRefCnt+1,pCollect->nElemRefMax); return NULL; } // TODO: Determine way of reporting compound elements in debug messages // If the element is stored in RAM: // - Copy the element into the internal RAM element array (asElem) // - Create an entry in the element reference array (asElemRef) that points // to the internal RAM element array. // - Since a copy has been made of the input element, the caller can // discard/reuse the pointer (pElem) after the call. // // If the element is stored in FLASH: // - Save the element pointer (pElem) into the element reference // array (asElemRef). // - Since the input pointer (pElem) has been recorded, the caller must // not discard/reuse the pointer. It should be defined as a static // variable. uint16_t nElemInd; uint16_t nElemRefInd; if ((eFlags & GSLC_ELEMREF_SRC) == GSLC_ELEMREF_SRC_RAM) { #if defined(DBG_LOG) GSLC_DEBUG_PRINT("INFO: Add elem to collection: ElemRef=%d/%d, ElemRAM=%d/%d, ElemId=%u (RAM)\n", pCollect->nElemRefCnt+1,pCollect->nElemRefMax,pCollect->nElemCnt+1,pCollect->nElemMax,pElem->nId); #endif // Ensure we have enough space in internal element array if (pCollect->nElemCnt+1 > (pCollect->nElemMax)) { GSLC_DEBUG2_PRINT("ERROR: CollectElemAdd() too many RAM elements (%d/%d), ElemID=%d\n", pCollect->nElemCnt+1,pCollect->nElemMax,pElem->nId); // TODO: Implement a function that returns the current page's ID so that // users can more easily identify the problematic page. return NULL; } // Copy the element to the internal array // - This performs a copy so that we can discard the element // pointer after the call is complete nElemInd = pCollect->nElemCnt; pCollect->asElem[nElemInd] = *pElem; pCollect->nElemCnt++; // Add a reference // - Pointer (pElem) links to an item of internal element array nElemRefInd = pCollect->nElemRefCnt; pCollect->asElemRef[nElemRefInd].eElemFlags = eFlags; pCollect->asElemRef[nElemRefInd].pElem = &(pCollect->asElem[nElemInd]); pCollect->nElemRefCnt++; } else { // External reference // - Pointer (pElem) links to an element stored in FLASH (must be declared statically) // Fetch a RAM copy of the FLASH element #if (GSLC_USE_PROGMEM) memcpy_P(&pGui->sElemTmpProg,pElem,sizeof(gslc_tsElem)); #endif #if defined(DBG_LOG) const gslc_tsElem* pElemRam = pElem; // Local element in RAM #if (GSLC_USE_PROGMEM) pElemRam = &pGui->sElemTmpProg; #endif GSLC_DEBUG_PRINT("INFO: Add elem to collection: ElemRef=%d/%d, ElemId=%u (FLASH)\n", pCollect->nElemRefCnt+1,pCollect->nElemRefMax,pElemRam->nId); #endif // Add a reference nElemRefInd = pCollect->nElemRefCnt; pCollect->asElemRef[nElemRefInd].eElemFlags = eFlags; pCollect->asElemRef[nElemRefInd].pElem = (gslc_tsElem*)pElem; // Typecast to drop const modifier pCollect->nElemRefCnt++; } // Fetch a pointer to the element reference array entry gslc_tsElemRef* pElemRef = &(pCollect->asElemRef[nElemRefInd]); // Mark any newly added element as requiring redraw gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL); // Return the new element reference return pElemRef; } bool gslc_CollectGetRedraw(gslc_tsGui* pGui,gslc_tsCollect* pCollect) { if (pCollect == NULL) { // ERROR return false; } // Determine if any sub-element in collection needs redraw // - This is generally used when deciding whether to redraw // a compound element uint16_t nInd; gslc_tsElemRef* pSubElemRef; bool bCollectRedraw = false; for (nInd=0;nIndnElemRefCnt;nInd++) { // Fetch the element pointer from the reference array pSubElemRef = &(pCollect->asElemRef[nInd]); if (gslc_ElemGetRedraw(pGui,pSubElemRef) != GSLC_REDRAW_NONE) { bCollectRedraw = true; break; } } return bCollectRedraw; } // Add an element to the collection associated with the page // // - Depending on the GSLC_ELEMREF_SRC_* setting, CollectElemAdd() // may either copy the contents of the element into the internal // RAM array or else just save a copy of the pointer (which // would mean that the pointer must not be invalidated). gslc_tsElemRef* gslc_ElemAdd(gslc_tsGui* pGui,int16_t nPageId,gslc_tsElem* pElem,gslc_teElemRefFlags eFlags) { if ((pGui == NULL) || (pElem == NULL)) { static const char GSLC_PMEM FUNCSTR[] = "ElemAdd"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return NULL; } if (nPageId == GSLC_PAGE_NONE) { // No page ID was provided, so skip add GSLC_DEBUG2_PRINT("ERROR: ElemAdd(%s) with PAGE_NONE\n",""); return NULL; } // Fetch the page containing the item gslc_tsPage* pPage = gslc_PageFindById(pGui,nPageId); if (pPage == NULL) { GSLC_DEBUG2_PRINT("ERROR: ElemAdd() page (ID=%d) was not found\n",nPageId); return NULL; } #if defined(DBG_LOG) // TODO: Determine way of reporting compound elements in debug messages GSLC_DEBUG_PRINT("INFO: Add elem to PageID=%d: ElemID=%d Rect=(X=%d,Y=%d,W=%d,H=%d)\n", nPageId,pElem->nId,pElem->rElem.x,pElem->rElem.y,pElem->rElem.w,pElem->rElem.h); #endif gslc_tsCollect* pCollect = &pPage->sCollect; gslc_tsElemRef* pElemRefAdd = gslc_CollectElemAdd(pGui,pCollect,pElem,eFlags); // Fetch access to the element from the reference // - This also handles the case with elements in FLASH gslc_tsElem* pElemLocal = NULL; pElemLocal = gslc_GetElemFromRef(pGui,pElemRefAdd); // Update the page's bounding rect gslc_UnionRect(&(pPage->rBounds), pElemLocal->rElem); return pElemRefAdd; } bool gslc_SetClipRect(gslc_tsGui* pGui,gslc_tsRect* pRect) { // Update the drawing clip rectangle if (pRect == NULL) { // Set to full size of screen return gslc_DrvSetClipRect(pGui,NULL); } else { // Set to user-specified region return gslc_DrvSetClipRect(pGui,pRect); } } void gslc_ElemSetImage(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,gslc_tsImgRef sImgRef, gslc_tsImgRef sImgRefSel) { gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (!pElem) return; // Update the normal and glowing images gslc_DrvSetElemImageNorm(pGui,pElem,sImgRef); gslc_DrvSetElemImageGlow(pGui,pElem,sImgRefSel); // Mark as needing redraw gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL); } bool gslc_SetBkgndImage(gslc_tsGui* pGui,gslc_tsImgRef sImgRef) { if (!gslc_DrvSetBkgndImage(pGui,sImgRef)) { return false; } gslc_PageFlipSet(pGui,true); return true; } bool gslc_SetBkgndColor(gslc_tsGui* pGui,gslc_tsColor nCol) { if (!gslc_DrvSetBkgndColor(pGui,nCol)) { return false; } gslc_PageFlipSet(pGui,true); return true; } bool gslc_SetTransparentColor(gslc_tsGui* pGui, gslc_tsColor nCol) { pGui->sTransCol = nCol; return true; } bool gslc_GuiRotate(gslc_tsGui* pGui, uint8_t nRotation) { // Simple wrapper for driver-specific rotation bool bOk = gslc_DrvRotate(pGui,nRotation); // Invalidate the new screen dimensions gslc_InvalidateRgnScreen(pGui); return bOk; } // Trigger a touch event on an element bool gslc_ElemSendEventTouch(gslc_tsGui* pGui,gslc_tsElemRef* pElemRefTracked, gslc_teTouch eTouch,int16_t nX,int16_t nY) { #if defined(DRV_TOUCH_NONE) return false; #else gslc_tsEventTouch sEventTouch; sEventTouch.eTouch = eTouch; sEventTouch.nX = nX; sEventTouch.nY = nY; gslc_tsEvent sEvent = gslc_EventCreate(pGui,GSLC_EVT_TOUCH,0,(void*)pElemRefTracked,&sEventTouch); gslc_ElemEvent((void*)pGui,sEvent); return true; #endif // !DRV_TOUCH_NONE } // Initialize the element struct to all zeros void gslc_ResetElem(gslc_tsElem* pElem) { if (pElem == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ResetElem"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } pElem->nFeatures = GSLC_ELEM_FEA_NONE; pElem->nFeatures &= ~GSLC_ELEM_FEA_VALID; // Init with Valid=false pElem->nFeatures &= ~GSLC_ELEM_FEA_CLICK_EN; pElem->nFeatures &= ~GSLC_ELEM_FEA_GLOW_EN; pElem->nFeatures &= ~GSLC_ELEM_FEA_FRAME_EN; pElem->nFeatures &= ~GSLC_ELEM_FEA_FILL_EN; pElem->nId = GSLC_ID_NONE; pElem->nType = GSLC_TYPE_BOX; pElem->nGroup = GSLC_GROUP_ID_NONE; pElem->rElem = (gslc_tsRect){0,0,0,0}; pElem->sImgRefNorm = gslc_ResetImage(); pElem->sImgRefGlow = gslc_ResetImage(); pElem->colElemFrame = GSLC_COL_WHITE; pElem->colElemFill = GSLC_COL_WHITE; pElem->colElemFrameGlow = GSLC_COL_WHITE; pElem->colElemFillGlow = GSLC_COL_WHITE; pElem->eTxtFlags = GSLC_TXT_DEFAULT; #if (GSLC_LOCAL_STR) pElem->pStrBuf[0] = '\0'; pElem->nStrBufMax = 0; #else pElem->pStrBuf = NULL; pElem->nStrBufMax = 0; #endif pElem->colElemText = GSLC_COL_WHITE; pElem->colElemTextGlow = GSLC_COL_WHITE; pElem->eTxtAlign = GSLC_ALIGN_MID_MID; pElem->nTxtMarginX = 0; pElem->nTxtMarginY = 0; pElem->pTxtFont = NULL; pElem->pXData = NULL; pElem->pfuncXEvent = NULL; // UNUSED pElem->pfuncXDraw = NULL; pElem->pfuncXTouch = NULL; pElem->pfuncXTick = NULL; #if (GSLC_FEATURE_COMPOUND) pElem->pElemRefParent = NULL; #endif } // Initialize the font struct to all zeros void gslc_ResetFont(gslc_tsFont* pFont) { if (pFont == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ResetFont"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } pFont->nId = GSLC_FONT_NONE; pFont->eFontRefType = GSLC_FONTREF_FNAME; pFont->eFontRefMode = GSLC_FONTREF_MODE_DEFAULT; pFont->pvFont = NULL; pFont->nSize = 0; } // Close down an element void gslc_ElemDestruct(gslc_tsElem* pElem) { if (pElem == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemDestruct"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } if (pElem->sImgRefNorm.pvImgRaw != NULL) { gslc_DrvImageDestruct(pElem->sImgRefNorm.pvImgRaw); pElem->sImgRefNorm = gslc_ResetImage(); } if (pElem->sImgRefGlow.pvImgRaw != NULL) { gslc_DrvImageDestruct(pElem->sImgRefGlow.pvImgRaw); pElem->sImgRefGlow = gslc_ResetImage(); } // TODO: Mark Element valid as false? // TODO: Add callback function so that // we can support additional closure actions // (eg. closing sub-elements of compound element). } // Close down a collection void gslc_CollectDestruct(gslc_tsGui* pGui,gslc_tsCollect* pCollect) { if (pCollect == NULL) { static const char GSLC_PMEM FUNCSTR[] = "CollectDestruct"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } uint16_t nInd; gslc_tsElem* pElem = NULL; for (nInd=0;nIndnElemRefCnt;nInd++) { gslc_tsElemRef* pElemRef = &pCollect->asElemRef[nInd]; gslc_teElemRefFlags eFlags = pElemRef->eElemFlags; // Only elements in RAM need destruction if ((eFlags & GSLC_ELEMREF_SRC) != GSLC_ELEMREF_SRC_RAM) { continue; } // Fetch the element pointer from the reference array pElem = gslc_GetElemFromRef(pGui,pElemRef); gslc_ElemDestruct(pElem); } pCollect->nElemRefCnt = 0; pCollect->nElemCnt = 0; } // Close down all in page void gslc_PageDestruct(gslc_tsGui* pGui,gslc_tsPage* pPage) { if (pPage == NULL) { static const char GSLC_PMEM FUNCSTR[] = "PageDestruct"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } gslc_tsCollect* pCollect = &pPage->sCollect; gslc_CollectDestruct(pGui,pCollect); } // Close down all GUI members, including pages and fonts void gslc_GuiDestruct(gslc_tsGui* pGui) { if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "GuiDestruct"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } // Loop through all pages in GUI uint8_t nPageInd; gslc_tsPage* pPage = NULL; for (nPageInd=0;nPageIndnPageCnt;nPageInd++) { pPage = &pGui->asPage[nPageInd]; gslc_PageDestruct(pGui,pPage); } pGui->nPageCnt = 0; // TODO: Consider moving into main element array if (pGui->sImgRefBkgnd.eImgFlags != GSLC_IMGREF_NONE) { gslc_DrvImageDestruct(pGui->sImgRefBkgnd.pvImgRaw); pGui->sImgRefBkgnd = gslc_ResetImage(); } // Close all fonts gslc_DrvFontsDestruct(pGui); // Close any driver-specific data gslc_DrvDestruct(pGui); } // ================================ // Private: Element Collection void gslc_CollectReset(gslc_tsCollect* pCollect,gslc_tsElem* asElem,uint16_t nElemMax, gslc_tsElemRef* asElemRef,uint16_t nElemRefMax) { if (pCollect == NULL) { static const char GSLC_PMEM FUNCSTR[] = "CollectReset"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } pCollect->nElemMax = nElemMax; pCollect->nElemCnt = 0; pCollect->nElemAutoIdNext = GSLC_ID_AUTO_BASE; //pCollect->pfuncXEvent = NULL; // UNUSED // Save the pointer to the element array pCollect->asElem = asElem; uint16_t nInd; for (nInd=0;nIndasElem[nInd])); } // Initialize element references pCollect->nElemRefMax = nElemRefMax; pCollect->nElemRefCnt = 0; pCollect->asElemRef = asElemRef; for (nInd=0;nIndasElemRef[nInd]).pElem = NULL; } // Reset touch / input tracking pCollect->pElemRefTracked = NULL; pCollect->nElemIndFocused = GSLC_IND_NONE; } bool gslc_CollectFindFocusStep(gslc_tsGui* pGui,gslc_tsCollect* pCollect,bool bNext,bool* pbWrapped,int16_t* pnElemInd) { #if !(GSLC_FEATURE_INPUT) return false; #else gslc_tsElemRef* pElemRef = NULL; gslc_tsElem* pElem = NULL; int16_t nIndStart; bool bFound = false; unsigned nElemIndCnt = pCollect->nElemRefCnt; int16_t nIndStep; int16_t nInd; int16_t nIndFocus = GSLC_IND_NONE; bool bCanFocus; *pbWrapped = false; *pnElemInd = GSLC_IND_NONE; nIndFocus = gslc_CollectGetFocus(pGui, pCollect); if (nIndFocus == GSLC_IND_NONE) { nIndStart = (bNext) ? 0 : (nElemIndCnt - 1); } else { nIndStart = (bNext) ? (nIndFocus + 1) : (nIndFocus - 1); } for (nIndStep=0;((!bFound)&&(nIndStep= nElemIndCnt) { *pbWrapped = true; nInd -= nElemIndCnt; } } else { nInd = nIndStart - nIndStep; // Detect wrap if (nInd < 0) { *pbWrapped = true; nInd += nElemIndCnt; } } // Get focus capability attribute bCanFocus = false; pElemRef = &(pCollect->asElemRef[nInd]); pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__); if (pElemRef->eElemFlags != GSLC_ELEMREF_NONE) { if (pElem == NULL) { GSLC_DEBUG2_PRINT("ERROR: eElemFlags not none, but pElem is NULL%s\n",""); exit(1); // FATAL } else { // Check the "click enable" flag if (pElem->nFeatures & GSLC_ELEM_FEA_CLICK_EN) { bCanFocus = true; } } } if (bCanFocus) { bFound = true; } } if (bFound) { *pnElemInd = nInd; } return bFound; #endif // GSLC_FEATURE_INPUT } // Search internal element array for one with a particular ID gslc_tsElemRef* gslc_CollectFindElemById(gslc_tsGui* pGui,gslc_tsCollect* pCollect,int16_t nElemId) { if (pCollect == NULL) { static const char GSLC_PMEM FUNCSTR[] = "CollectFindElemById"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return NULL; } gslc_tsElem* pElem = NULL; gslc_tsElemRef* pElemRef = NULL; gslc_tsElemRef* pFoundElemRef = NULL; uint16_t nInd; if (nElemId == GSLC_ID_TEMP) { // ERROR: Don't expect to do this GSLC_DEBUG2_PRINT("ERROR: CollectFindElemById(%s) searching for temp ID\n",""); return NULL; } for (nInd=0;nIndnElemRefCnt;nInd++) { // Fetch the element pointer from the reference array pElemRef = &(pCollect->asElemRef[nInd]); pElem = gslc_GetElemFromRef(pGui,pElemRef); if (pElem->nId == nElemId) { pFoundElemRef = pElemRef; break; } } return pFoundElemRef; } int gslc_CollectGetNextId(gslc_tsGui* pGui,gslc_tsCollect* pCollect) { int16_t nElemId = pCollect->nElemAutoIdNext; pCollect->nElemAutoIdNext++; return nElemId; } gslc_tsElemRef* gslc_CollectGetElemRefTracked(gslc_tsGui* pGui,gslc_tsCollect* pCollect) { return pCollect->pElemRefTracked; } void gslc_CollectSetElemTracked(gslc_tsGui* pGui,gslc_tsCollect* pCollect,gslc_tsElemRef* pElemRef) { pCollect->pElemRefTracked = pElemRef; } // Find an element index in a collection from a coordinate // - Note that the search is in decreasing Z-order (ie. front to back) // so that we effectively find the top-most element that should // receive an event. gslc_tsElemRef* gslc_CollectFindElemFromCoord(gslc_tsGui* pGui,gslc_tsCollect* pCollect,int16_t nX, int16_t nY) { int16_t nInd; bool bFound = false; gslc_tsElemRef* pElemRef = NULL; gslc_tsElemRef* pFoundElemRef = NULL; if (pCollect->nElemRefCnt == 0) { return NULL; } for (nInd=pCollect->nElemRefCnt-1;nInd>=0;nInd--) { pElemRef = &(pCollect->asElemRef[nInd]); if (!gslc_ElemGetVisible(pGui, pElemRef)) { // Element is marked as hidden, so don't accept touch continue; } bFound = gslc_ElemOwnsCoord(pGui,pElemRef,nX,nY,true); if (bFound) { pFoundElemRef = pElemRef; // Stop searching break; } } // Return pointer or NULL if none found return pFoundElemRef; } int16_t gslc_CollectGetFocus(gslc_tsGui* pGui,gslc_tsCollect* pCollect) { return pCollect->nElemIndFocused; } void gslc_CollectSetFocus(gslc_tsGui* pGui, gslc_tsCollect* pCollect, int16_t nElemInd) { pCollect->nElemIndFocused = nElemInd; } #if (GSLC_FEATURE_COMPOUND) // Go through all elements in a collection and set the parent // element pointer. void gslc_CollectSetParent(gslc_tsGui* pGui,gslc_tsCollect* pCollect,gslc_tsElemRef* pElemRefParent) { gslc_tsElemRef* pElemRef = NULL; gslc_tsElem* pElem = NULL; uint16_t nInd; for (nInd=0;nIndnElemRefCnt;nInd++) { gslc_teElemRefFlags eFlags = pCollect->asElemRef[nInd].eElemFlags; // NOTE: Only elements in RAM are updated // TODO: Error handling if we attempt to modify FLASH-based element if ((eFlags & GSLC_ELEMREF_SRC) != GSLC_ELEMREF_SRC_RAM) { continue; } pElemRef = &pCollect->asElemRef[nInd]; pElem = gslc_GetElemFromRef(pGui,pElemRef); pElem->pElemRefParent = pElemRefParent; } } #endif /* UNUSED void gslc_CollectSetEventFunc(gslc_tsGui* pGui,gslc_tsCollect* pCollect,GSLC_CB_EVENT funcCb) { if ((pCollect == NULL) || (funcCb == NULL)) { static const char GSLC_PMEM FUNCSTR[] = "CollectSetEventFunc"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } pCollect->pfuncXEvent = funcCb; } */ // ============================================================================ // Trigonometric lookup table for sin(x) // - The lookup table saves us from having to import the sin/cos // functions and the associated floating point libraries. GUIslice // has provided a relatively lightweight fixed-point representation // using the following 16-bit LUT (8-bit index). // - At this point in time, the LUT is only used by GUIslice for sin/cos // in supporting the XRadial controls // - The LUT consumes approx 514 bytes of FLASH memory. FIXME: Ensure in FLASH! uint16_t m_nLUTSinF0X16[257] = { 0x0000,0x0192,0x0324,0x04B6,0x0648,0x07DA,0x096C,0x0AFD,0x0C8F,0x0E21,0x0FB2,0x1143,0x12D5,0x1465,0x15F6,0x1787, 0x1917,0x1AA7,0x1C37,0x1DC6,0x1F56,0x20E5,0x2273,0x2402,0x258F,0x271D,0x28AA,0x2A37,0x2BC3,0x2D4F,0x2EDB,0x3066, 0x31F1,0x337B,0x3505,0x368E,0x3816,0x399E,0x3B26,0x3CAD,0x3E33,0x3FB9,0x413E,0x42C3,0x4447,0x45CA,0x474C,0x48CE, 0x4A4F,0x4BD0,0x4D4F,0x4ECE,0x504D,0x51CA,0x5347,0x54C3,0x563E,0x57B8,0x5931,0x5AAA,0x5C21,0x5D98,0x5F0E,0x6083, 0x61F7,0x636A,0x64DC,0x664D,0x67BD,0x692C,0x6A9A,0x6C07,0x6D73,0x6EDE,0x7048,0x71B1,0x7319,0x747F,0x75E5,0x7749, 0x78AC,0x7A0F,0x7B6F,0x7CCF,0x7E2E,0x7F8B,0x80E7,0x8242,0x839B,0x84F3,0x864A,0x87A0,0x88F5,0x8A48,0x8B99,0x8CEA, 0x8E39,0x8F86,0x90D3,0x921E,0x9367,0x94AF,0x95F6,0x973B,0x987F,0x99C1,0x9B02,0x9C41,0x9D7F,0x9EBB,0x9FF6,0xA12F, 0xA266,0xA39D,0xA4D1,0xA604,0xA735,0xA865,0xA993,0xAABF,0xABEA,0xAD13,0xAE3B,0xAF60,0xB085,0xB1A7,0xB2C8,0xB3E7, 0xB504,0xB61F,0xB739,0xB851,0xB967,0xBA7B,0xBB8E,0xBC9F,0xBDAE,0xBEBB,0xBFC6,0xC0D0,0xC1D7,0xC2DD,0xC3E1,0xC4E3, 0xC5E3,0xC6E1,0xC7DD,0xC8D7,0xC9D0,0xCAC6,0xCBBB,0xCCAD,0xCD9E,0xCE8C,0xCF79,0xD063,0xD14C,0xD232,0xD317,0xD3F9, 0xD4DA,0xD5B8,0xD695,0xD76F,0xD847,0xD91D,0xD9F1,0xDAC3,0xDB93,0xDC60,0xDD2C,0xDDF5,0xDEBD,0xDF82,0xE045,0xE106, 0xE1C4,0xE281,0xE33B,0xE3F3,0xE4A9,0xE55D,0xE60E,0xE6BD,0xE76A,0xE815,0xE8BE,0xE964,0xEA08,0xEAAA,0xEB4A,0xEBE7, 0xEC82,0xED1B,0xEDB1,0xEE45,0xEED7,0xEF67,0xEFF4,0xF07F,0xF108,0xF18E,0xF212,0xF294,0xF313,0xF390,0xF40A,0xF483, 0xF4F9,0xF56C,0xF5DD,0xF64C,0xF6B9,0xF723,0xF78A,0xF7F0,0xF853,0xF8B3,0xF911,0xF96D,0xF9C6,0xFA1D,0xFA72,0xFAC4, 0xFB13,0xFB61,0xFBAB,0xFBF4,0xFC3A,0xFC7D,0xFCBE,0xFCFD,0xFD39,0xFD73,0xFDAA,0xFDDF,0xFE12,0xFE42,0xFE6F,0xFE9A, 0xFEC3,0xFEE9,0xFF0D,0xFF2E,0xFF4D,0xFF69,0xFF83,0xFF9B,0xFFB0,0xFFC2,0xFFD2,0xFFE0,0xFFEB,0xFFF3,0xFFFA,0xFFFD, 0xFFFF, };