serial_debugger/hardware/_controller/src/guislice/GUIslice.c

5141 lines
161 KiB
C

// =======================================================================
// 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 <stdio.h>
#if defined(DBG_REDRAW)
#include <Arduino.h> // For delay()
#endif // DBG_REDRAW
#ifdef DBG_FRAME_RATE
#include <time.h> // for FrameRate reporting
#endif
#if (GSLC_USE_FLOAT)
#include <math.h>
#endif
#if (GSLC_USE_PROGMEM)
#include <avr/pgmspace.h> // For memcpy_P()
#endif
#include <stdarg.h> // 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;((nInputInd<nInputMax)&&(!bFound));nInputInd++) {
sMapEntry = pGui->asInputMap[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;nPageInd<pGui->nPageCnt;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 = (nRX0<nCX0)? nCX0 : nRX0;
nRY0 = (nRY0<nCY0)? nCY0 : nRY0;
nRX1 = (nRX1>nCX1)? 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;nOffset<nW;nOffset++) {
gslc_DrvDrawPoint(pGui,nX+nOffset,nY,nCol);
}
gslc_PageFlipSet(pGui,true);
}
void gslc_DrawLineV(gslc_tsGui* pGui,int16_t nX, int16_t nY, uint16_t nH,gslc_tsColor nCol)
{
uint16_t nOffset;
for (nOffset=0;nOffset<nH;nOffset++) {
gslc_DrvDrawPoint(pGui,nX,nY+nOffset,nCol);
}
gslc_PageFlipSet(pGui,true);
}
// Note that angle is in degrees * 64
void gslc_DrawLinePolar(gslc_tsGui* pGui,int16_t nX,int16_t nY,uint16_t nRadStart,uint16_t nRadEnd,int16_t n64Ang,gslc_tsColor nCol)
{
// Draw the ray representing the current value
int16_t nDxS = (int32_t)nRadStart * gslc_sinFX(n64Ang)/32768;
int16_t nDyS = (int32_t)nRadStart * gslc_cosFX(n64Ang)/32768;
int16_t nDxE = (int32_t)nRadEnd * gslc_sinFX(n64Ang)/32768;
int16_t nDyE = (int32_t)nRadEnd * gslc_cosFX(n64Ang)/32768;
gslc_DrawLine(pGui,nX+nDxS,nY-nDyS,nX+nDxE,nY-nDyE,nCol);
}
void gslc_DrawFrameRect(gslc_tsGui* pGui,gslc_tsRect rRect,gslc_tsColor nCol)
{
// Ensure dimensions are valid
if ((rRect.w == 0) || (rRect.h == 0)) {
return;
}
#if (DRV_HAS_DRAW_RECT_FRAME)
// Call optimized driver implementation
gslc_DrvDrawFrameRect(pGui,rRect,nCol);
#else
// Emulate rect frame with four lines
int16_t nX,nY;
uint16_t nH,nW;
nX = rRect.x;
nY = rRect.y;
nW = rRect.w;
nH = rRect.h;
gslc_DrawLineH(pGui,nX,nY,nW-1,nCol); // Top
gslc_DrawLineH(pGui,nX,(int16_t)(nY+nH-1),nW-1,nCol); // Bottom
gslc_DrawLineV(pGui,nX,nY,nH-1,nCol); // Left
gslc_DrawLineV(pGui,(int16_t)(nX+nW-1),nY,nH-1,nCol); // Right
#endif
gslc_PageFlipSet(pGui,true);
}
void gslc_DrawFrameRoundRect(gslc_tsGui* pGui,gslc_tsRect rRect,int16_t nRadius,gslc_tsColor nCol)
{
// Ensure dimensions are valid
if ((rRect.w == 0) || (rRect.h == 0)) {
return;
}
#if (DRV_HAS_DRAW_RECT_ROUND_FRAME)
// Call optimized driver implementation
gslc_DrvDrawFrameRoundRect(pGui,rRect,nRadius,nCol);
#else
// TODO: Add emulation of rounded rects. For now fallback to square corners
gslc_DrvDrawFrameRect(pGui,rRect,nCol);
#endif
gslc_PageFlipSet(pGui,true);
}
void gslc_DrawFillRect(gslc_tsGui* pGui,gslc_tsRect rRect,gslc_tsColor nCol)
{
// Ensure dimensions are valid
if ((rRect.w == 0) || (rRect.h == 0)) {
return;
}
#if (DRV_HAS_DRAW_RECT_FILL)
// Call optimized driver implementation
gslc_DrvDrawFillRect(pGui,rRect,nCol);
#else
// Emulate it with individual line draws
// TODO: This should be avoided as it will generally be very inefficient
int nRow;
for (nRow=0;nRow<rRect.h;nRow++) {
gslc_DrawLineH(pGui, rRect.x, rRect.y+nRow, rRect.w, nCol);
}
#endif
gslc_PageFlipSet(pGui,true);
}
void gslc_DrawFillRoundRect(gslc_tsGui* pGui,gslc_tsRect rRect,int16_t nRadius,gslc_tsColor nCol)
{
// Ensure dimensions are valid
if ((rRect.w == 0) || (rRect.h == 0)) {
return;
}
#if (DRV_HAS_DRAW_RECT_ROUND_FILL)
// Call optimized driver implementation
gslc_DrvDrawFillRoundRect(pGui,rRect,nRadius,nCol);
#else
// TODO: Add emulation of rounded rects. For now fallback to square corners
gslc_DrvDrawFillRect(pGui,rRect,nCol);
#endif
gslc_PageFlipSet(pGui,true);
}
// Expand or contract a rectangle in width and/or height (equal
// amounts on both side), based on the centerpoint of the rectangle.
gslc_tsRect gslc_ExpandRect(gslc_tsRect rRect,int16_t nExpandW,int16_t nExpandH)
{
gslc_tsRect rNew = {0,0,0,0};
// Detect error case of contracting region too far
if ( ((int16_t)rRect.w < (-2*nExpandW)) || ((int16_t)rRect.h < (-2*nExpandH)) ) {
// Return an empty coordinate box (which won't be drawn)
//GSLC_DEBUG2_PRINT("ERROR: ExpandRect(%d,%d) contracts too far\n",nExpandW,nExpandH);
return rNew;
}
// Adjust the new width/height
// Note that the overall width/height changes by a factor of
// two since we are applying the adjustment on both sides (ie.
// top/bottom or left/right) equally.
rNew.w = rRect.w + (2*nExpandW);
rNew.h = rRect.h + (2*nExpandH);
// Adjust the rectangle coordinate to allow for new dimensions
// Note that this moves the coordinate in the opposite
// direction of the expansion/contraction.
rNew.x = rRect.x - nExpandW;
rNew.y = rRect.y - nExpandH;
return rNew;
}
// Expand the current rect (pRect) to enclose the additional rect region (rAddRect)
void gslc_UnionRect(gslc_tsRect* pRect,gslc_tsRect rAddRect)
{
int16_t nSrcX0, nSrcY0, nSrcX1, nSrcY1;
int16_t nAddX0, nAddY0, nAddX1, nAddY1;
// If the source rect has zero dimensions, then treat as empty
if ((pRect->w == 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<nY01;nYos++) {
// Determine row endpoints (no rounding)
//nXa = (nYos )*(nX0-nX1)/(nY0-nY1);
//nXb = (nYos-(nY0-nY1))*(nX2-nX0)/(nY2-nY0);
// Determine row endpoints (using rounding)
nXa = 2*(nYos)*nX01;
nXa += (nXa>=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;nFontInd<pGui->nFontCnt;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;nStackPage<GSLC_STACK__MAX;nStackPage++) {
// Select the page collection to process
pPage = pGui->apPageStack[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;nInd<pCollect->nElemRefCnt;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;nInd<pGui->nPageMax;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;nInd<pCollect->nElemRefCnt;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;nInd<pCollect->nElemRefCnt;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;nInd<pCollect->nElemRefCnt;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;nPageInd<pGui->nPageCnt;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;nInd<nElemMax;nInd++) {
gslc_ResetElem(&(pCollect->asElem[nInd]));
}
// Initialize element references
pCollect->nElemRefMax = nElemRefMax;
pCollect->nElemRefCnt = 0;
pCollect->asElemRef = asElemRef;
for (nInd=0;nInd<nElemMax;nInd++) {
(pCollect->asElemRef[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));nIndStep++) {
if (bNext) {
nInd = nIndStep + nIndStart;
// Detect wrap
if (nInd >= 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;nInd<pCollect->nElemRefCnt;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;nInd<pCollect->nElemRefCnt;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,
};