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

983 lines
30 KiB
C

// =======================================================================
// GUIslice library (extensions)
// - Calvin Hass
// - https://www.impulseadventure.com/elec/guislice-gui.html
// - https://github.com/ImpulseAdventure/GUIslice
// =======================================================================
//
// The MIT License
//
// Copyright 2016-2020 Calvin Hass
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// =======================================================================
/// \file XListbox.c
// GUIslice library
#include "GUIslice.h"
#include "GUIslice_drv.h"
#include "XListbox.h"
#include <stdio.h>
#if (GSLC_USE_PROGMEM)
#include <avr/pgmspace.h>
#endif
// ----------------------------------------------------------------------------
// Error Messages
// ----------------------------------------------------------------------------
extern const char GSLC_PMEM ERRSTR_NULL[];
extern const char GSLC_PMEM ERRSTR_PXD_NULL[];
// ----------------------------------------------------------------------------
// Extended element definitions
// ----------------------------------------------------------------------------
//
// - This file extends the core GUIslice functionality with
// additional widget types
// ----------------------------------------------------------------------------
// TODO: Combine with GUIslice MAX_STR
// Defines the maximum length of a listbox item
#define XLISTBOX_MAX_STR 20
// ============================================================================
// Extended Element: Listbox
// - A Listbox control
// ============================================================================
// Basic debug functionality
/*
void debug_ElemXListboxDump(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef)
{
gslc_tsElem* pElem = gslc_GetElemFromRef(pGui, pElemRef);
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)(pElem->pXData);
// Prevent runaway looping due to damaged buffer
// Don't ask why I know to do this - PaulC
int16_t nSafety=0;
char acTxt[50 + 1];
GSLC_DEBUG2_PRINT("Xlistbox:Dump (nBufPos=%d nItemCnt=%d)\n", pListbox->nBufItemsPos,pListbox->nItemCnt);
for (unsigned nInd = 0; nInd < pListbox->nBufItemsPos; nInd++) {
nSafety++;
if (nSafety > pListbox->nBufItemsMax) break;
char ch = pListbox->pBufItems[nInd];
if (ch == 0) {
snprintf(acTxt, 40,"0x%04x: [%2u] = %02X = [NULL]", (char*)&pListbox->pBufItems[nInd],nInd, ch);
}
else {
snprintf(acTxt, 40,"0x%04x: [%2u] = %02X = [%c]", (char*)&pListbox->pBufItems[nInd],nInd, ch, ch);
}
GSLC_DEBUG2_PRINT("XListbox:Dump: %s\n", acTxt);
}
}
*/
char* gslc_ElemXListboxGetItemAddr(gslc_tsXListbox* pListbox, int16_t nItemCurSel)
{
char* pBuf = NULL;
uint16_t nBufPos = 0;
uint16_t nItemInd = 0;
bool bFound = false;
while (1) {
if (nItemInd == nItemCurSel) {
bFound = true;
break;
}
if (nBufPos >= pListbox->nBufItemsMax) {
break;
}
if (pListbox->pBufItems[nBufPos] == 0) {
nItemInd++;
}
nBufPos++;
}
if (bFound) {
pBuf = (char*)&(pListbox->pBufItems[nBufPos]);
return pBuf;
} else {
return NULL;
}
}
// Recalculate listbox item sizing if enabled
bool gslc_ElemXListboxRecalcSize(gslc_tsXListbox* pListbox,gslc_tsRect rElem)
{
int16_t nElem;
int16_t nElemInner;
int16_t nItemOuter;
int16_t nItemWOld;
bool bNeedRedraw = false;
// If number of rows was auto-sized based on content, then calculate now
if (pListbox->nRows == XLISTBOX_SIZE_AUTO) {
if (pListbox->nItemCnt == 0) {
pListbox->nRows = 1; // Force at least one row
} else {
pListbox->nRows = ((pListbox->nItemCnt + 1) / pListbox->nCols);
}
}
// NOTE: In the nElemInner calculation, we add nItemGap to account
// for the fact that the last column does not include a "gap" after it.
if (pListbox->bItemAutoSizeW) {
nItemWOld = pListbox->nItemW;
nElem = rElem.w;
nElemInner = nElem - (2 * pListbox->nMarginW) + pListbox->nItemGap;
nItemOuter = nElemInner / pListbox->nCols;
pListbox->nItemW = nItemOuter - pListbox->nItemGap;
if (pListbox->nItemW != nItemWOld) {
bNeedRedraw = true;
}
}
if (pListbox->bItemAutoSizeH) {
nItemWOld = pListbox->nItemH;
nElem = rElem.h;
nElemInner = nElem - (2 * pListbox->nMarginH) + pListbox->nItemGap;
nItemOuter = nElemInner / pListbox->nRows;
pListbox->nItemH = nItemOuter - pListbox->nItemGap;
if (pListbox->nItemH != nItemWOld) {
bNeedRedraw = true;
}
}
// Clear Need Recalc flag
pListbox->bNeedRecalc = false;
return bNeedRedraw;
}
void gslc_ElemXListboxSetSize(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int8_t nRows, int8_t nCols)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return;
pListbox->nRows = nRows;
pListbox->nCols = nCols;
pListbox->bNeedRecalc = true;
// Mark as needing full redraw
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL);
}
void gslc_ElemXListboxSetMargin(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int8_t nMarginW, int8_t nMarginH)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return;
pListbox->nMarginW = nMarginW;
pListbox->nMarginH = nMarginH;
pListbox->bNeedRecalc = true;
// Mark as needing full redraw
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL);
}
void gslc_ElemXListboxItemsSetSize(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int16_t nItemW, int16_t nItemH)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return;
pListbox->nItemW = nItemW;
pListbox->nItemH = nItemH;
// Determine if auto-sizing requested
pListbox->bItemAutoSizeW = (nItemW == XLISTBOX_SIZE_AUTO) ? true : false;
pListbox->bItemAutoSizeH = (nItemH == XLISTBOX_SIZE_AUTO) ? true : false;
pListbox->bNeedRecalc = true;
// Mark as needing full redraw
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL);
}
void gslc_ElemXListboxItemsSetGap(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int8_t nGap, gslc_tsColor colGap)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return;
pListbox->nItemGap = nGap;
pListbox->colGap = colGap;
pListbox->bNeedRecalc = true;
// Mark as needing full redraw
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL);
}
void gslc_ElemXListboxReset(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return;
pListbox->nBufItemsPos = 0;
pListbox->nItemCnt = 0;
pListbox->nItemCurSel = XLISTBOX_SEL_NONE;
pListbox->bNeedRecalc = true;
// Mark as needing full redraw
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL);
}
bool gslc_ElemXListboxAddItem(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, const char* pStrItem)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return false;
int8_t nStrItemLen;
char* pBuf = NULL;
uint16_t nBufItemsPos = pListbox->nBufItemsPos;
uint16_t nBufItemsMax = pListbox->nBufItemsMax;
nStrItemLen = strlen(pStrItem);
if (nStrItemLen == 0) {
// Nothing to add
return false;
}
// Ensure we won't overrun the buffer, including the terminator
if (nBufItemsPos + nStrItemLen + 1 > nBufItemsMax) {
// Proceed with truncation
nStrItemLen = nBufItemsMax - pListbox->nBufItemsPos - 1;
GSLC_DEBUG2_PRINT("ERROR: ElemXListboxAddItem() buffer too small\n", "");
}
// If the truncation left nothing to add, skip out now
if (nStrItemLen <= 0) {
return false;
}
// Treat this section of the buffer as [char]
pBuf = (char*)pListbox->pBufItems + nBufItemsPos;
strncpy(pBuf, pStrItem, nStrItemLen);
pListbox->nBufItemsPos += nStrItemLen;
// Ensure terminator added
pBuf += nStrItemLen;
*pBuf = 0;
pListbox->nBufItemsPos++;
pListbox->nItemCnt++;
//GSLC_DEBUG2_PRINT("Xlistbox:Add\n", "");
//debug_ElemXListboxDump(pGui, pElemRef);
// Inidicate sizing may need update
pListbox->bNeedRecalc = true;
// Mark as needing full redraw
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL);
return true;
}
bool gslc_ElemXListboxInsertItemAt(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, uint16_t nInsertPos,
const char* pStrItem)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return false;
int8_t nStrItemLen;
char* pBuf = NULL;
uint16_t nBufItemsPos = pListbox->nBufItemsPos;
uint16_t nBufItemsMax = pListbox->nBufItemsMax;
if (nInsertPos > pListbox->nItemCnt) {
GSLC_DEBUG2_PRINT("ERROR: ElemXListboxInsertItemAt() Current Count: %d Invalid Position %d\n",
pListbox->nItemCnt,nInsertPos);
return false;
}
nStrItemLen = strlen(pStrItem);
if (nStrItemLen == 0) {
// Nothing to add
return false;
}
// Ensure we won't overrun the buffer, including the terminator
if (nBufItemsPos + nStrItemLen + 1 > nBufItemsMax) {
GSLC_DEBUG2_PRINT("ERROR: ElemXListboxInsertItemAt() buffer too small\n", "");
return false;
}
// If the truncation left nothing to add, skip out now
if (nStrItemLen <= 0) {
return false;
}
// GSLC_DEBUG2_PRINT("Xlistbox:InsertAt: %d\n", nInsertPos);
// debug_ElemXListboxDump(pGui, pElemRef);
pBuf = gslc_ElemXListboxGetItemAddr(pListbox, nInsertPos);
// If position is incorrect, bail out...
if (pBuf == NULL) {
return false;
}
// Make a hole in the buffer to slot in the new item
char* pSrc = (char*)pListbox->pBufItems+nBufItemsPos-1;
char* pDest = pSrc+nStrItemLen+1;
int16_t nMoveLen = (int16_t)(pSrc - pBuf)+1;
uint8_t ch;
for (uint16_t nInd = 0; nInd < nMoveLen; nInd++) {
ch = *pSrc;
*pDest = ch;
pDest--;
pSrc--;
}
// Now slot in the new item
memcpy(pBuf, pStrItem, nStrItemLen+1);
// update our buffer information
pListbox->nBufItemsPos += nStrItemLen+1;
pListbox->nItemCnt++;
// GSLC_DEBUG2_PRINT("Xlistbox:After InsertAt %d\n", nInsertPos);
// debug_ElemXListboxDump(pGui, pElemRef);
// Indicate sizing may need update
pListbox->bNeedRecalc = true;
// Mark as needing full redraw
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL);
return true;
}
bool gslc_ElemXListboxDeleteItemAt(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, uint16_t nDeletePos)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return false;
int8_t nStrItemLen;
char* pBuf = NULL;
uint16_t nBufItemsPos = pListbox->nBufItemsPos;
// GSLC_DEBUG2_PRINT("Xlistbox:DeleteAt: %d\n", nDeletePos);
// debug_ElemXListboxDump(pGui, pElemRef);
pBuf = gslc_ElemXListboxGetItemAddr(pListbox, nDeletePos);
// If position is incorrect, bail out...
if (pBuf == NULL) {
return false;
}
nStrItemLen = strlen(pBuf);
// Pull items after this delete position up to delete this item
char* pSrc = (char*)pBuf+nStrItemLen+1;
char* pDest = (char*)pBuf;
char* pEndOfBuf = (char*)(pListbox->pBufItems+nBufItemsPos);
int16_t nMoveLen = (int16_t)(pEndOfBuf-pSrc)+1;
uint8_t ch;
if (nMoveLen > 1) {
for (uint16_t nInd = 0; nInd < nMoveLen; nInd++) {
ch = *pSrc;
*pDest = ch;
pDest++;
pSrc++;
}
}
// update our buffer information
pListbox->nBufItemsPos -= nStrItemLen+1;
pListbox->nItemCnt--;
// unselect item
gslc_ElemXListboxSetSel(pGui, pElemRef, XLISTBOX_SEL_NONE);
// GSLC_DEBUG2_PRINT("Xlistbox:After DeleteAt %d\n", nDeletePos);
// debug_ElemXListboxDump(pGui, pElemRef);
// Indicate sizing may need update
pListbox->bNeedRecalc = true;
// Mark as needing full redraw
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL);
return true;
}
bool gslc_ElemXListboxGetItem(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int16_t nItemCurSel, char* pStrItem,
uint8_t nStrItemLen)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return false;
// Ensure user provided valid string
if ((pStrItem == NULL) || (nStrItemLen == 0)) {
// ERROR
return false;
}
uint16_t nBufPos = 0;
uint16_t nItemInd = 0;
uint8_t* pBuf = NULL;
bool bFound = false;
while (1) {
if (nItemInd == nItemCurSel) {
bFound = true;
break;
}
if (nBufPos >= pListbox->nBufItemsMax) {
break;
}
if (pListbox->pBufItems[nBufPos] == 0) {
nItemInd++;
}
nBufPos++;
}
if (bFound) {
pBuf = &(pListbox->pBufItems[nBufPos]);
strncpy(pStrItem, (char*)pBuf, nStrItemLen);
// Ensure null terminated in case the buffer
// was smaller than the item source
pStrItem[nStrItemLen - 1] = 0;
return true;
} else {
// If no item was found, return an empty string (NULL)
pStrItem[0] = 0;
return false;
}
}
int16_t gslc_ElemXListboxGetItemCnt(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return 0;
return pListbox->nItemCnt;
}
// Create a Listbox element and add it to the GUI element list
// - Defines default styling for the element
// - Defines callback for redraw and touch
gslc_tsElemRef* gslc_ElemXListboxCreate(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage,
gslc_tsXListbox* pXData,gslc_tsRect rElem,int16_t nFontId,uint8_t* pBufItems,uint16_t nBufItemsMax,int16_t nItemDefault)
{
if ((pGui == NULL) || (pXData == NULL)) {
static const char GSLC_PMEM FUNCSTR[] = "ElemXListboxCreate";
GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
return NULL;
}
gslc_tsElem sElem;
gslc_tsElemRef* pElemRef = NULL;
sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPEX_LISTBOX,rElem,NULL,0,nFontId);
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.nGroup = GSLC_GROUP_ID_NONE;
pXData->pBufItems = pBufItems;
pXData->nBufItemsMax = nBufItemsMax;
pXData->nBufItemsPos = 0;
pXData->nItemCnt = 0;
pXData->nItemCurSel = nItemDefault;
pXData->nItemCurSelLast = XLISTBOX_SEL_NONE;
pXData->nItemSavedSel = XLISTBOX_SEL_NONE;
pXData->nItemTop = 0;
pXData->pfuncXSel = NULL;
pXData->nCols = 1;
pXData->nRows = XLISTBOX_SIZE_AUTO; // Auto-calculated from content
pXData->bNeedRecalc = true; // Force auto-sizing
pXData->nMarginW = 5;
pXData->nMarginH = 5;
pXData->bItemAutoSizeW = true; // Force auto-sizing
pXData->bItemAutoSizeH = false;
pXData->nItemW = XLISTBOX_SIZE_AUTO; // Auto-sized
pXData->nItemH = 30;
pXData->nItemGap = 2;
pXData->colGap = GSLC_COL_BLACK;
pXData->nItemCurSelLast = XLISTBOX_SEL_NONE;
sElem.pXData = (void*)(pXData);
// Specify the custom drawing callback
sElem.pfuncXDraw = &gslc_ElemXListboxDraw;
// Specify the custom touch tracking callback
sElem.pfuncXTouch = &gslc_ElemXListboxTouch;
sElem.colElemFill = GSLC_COL_BLACK;
sElem.colElemFillGlow = GSLC_COL_BLACK;
sElem.colElemFrame = GSLC_COL_GRAY;
sElem.colElemFrameGlow = GSLC_COL_WHITE;
// Set default text alignment:
// - Vertical center, left justify
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;
}
// Redraw the Listbox
// - Note that this redraw is for the entire element rect region
// - The Draw function parameters use void pointers to allow for
// simpler callback function definition & scalability.
bool gslc_ElemXListboxDraw(void* pvGui,void* pvElemRef,gslc_teRedrawType eRedraw)
{
// Typecast the parameters to match the GUI and element types
gslc_tsGui* pGui = (gslc_tsGui*)(pvGui);
gslc_tsElemRef* pElemRef = (gslc_tsElemRef*)(pvElemRef);
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return false;
gslc_tsElem* pElem = gslc_GetElemFromRef(pGui,pElemRef);
//bool bGlow = (pElem->nFeatures & GSLC_ELEM_FEA_GLOW_EN) && gslc_ElemGetGlow(pGui,pElemRef);
int8_t nItemCurSel = pListbox->nItemCurSel;
gslc_tsRect rElemRect;
if (pElem->nFeatures & GSLC_ELEM_FEA_FRAME_EN) {
rElemRect = gslc_ExpandRect(pElem->rElem, -1, -1);
if (eRedraw == GSLC_REDRAW_FULL) {
gslc_DrawFrameRect(pGui, pElem->rElem, pElem->colElemFrame);
}
} else {
rElemRect = pElem->rElem;
}
// If full redraw and gap is enabled:
// - Clear background with gap color, as list items
// will be overdrawn in colBg color
if (eRedraw == GSLC_REDRAW_FULL) {
if (pListbox->nItemGap > 0) {
gslc_DrawFillRect(pGui, rElemRect, pListbox->colGap);
}
}
// Determine if we need to recalculate the item sizing
if (pListbox->bNeedRecalc) {
gslc_ElemXListboxRecalcSize(pListbox, rElemRect);
}
int16_t nX0, nY0;
nX0 = rElemRect.x;
nY0 = rElemRect.y;
int8_t nRows = pListbox->nRows;
int8_t nCols = pListbox->nCols;
gslc_tsRect rItemRect;
int16_t nItemBaseX, nItemBaseY;
int16_t nItemX, nItemY;
int16_t nItemW, nItemH;
bool bItemSel;
gslc_tsColor colFill, colTxt;
// Error check
if (nCols == 0) {
return false;
}
// Determine top-left coordinate of list matrix
nItemBaseX = nX0 + pListbox->nMarginW;
nItemBaseY = nY0 + pListbox->nMarginH;
char acStr[XLISTBOX_MAX_STR+1] = "";
// Loop through the items in the list
int8_t nItemTop = pListbox->nItemTop;
int8_t nItemCnt = pListbox->nItemCnt;
int8_t nItemInd;
// Note that nItemTop is always pointing to an
// item index at the start of a row
// Determine the list indices to display in the visible window due to scrolling
int8_t nDispIndMax = (nRows * nCols);
for (int8_t nDispInd = 0; nDispInd < nDispIndMax; nDispInd++) {
// Calculate the item index based on the display index
nItemInd = nItemTop + nDispInd;
// Did we go past the end of our list?
if (nItemInd >= nItemCnt) {
break;
}
// Fetch the list item
bool bOk = gslc_ElemXListboxGetItem(pGui, pElemRef, nItemInd, acStr, XLISTBOX_MAX_STR);
if (!bOk) {
// TODO: Erorr handling
break;
}
int8_t nItemIndX, nItemIndY;
int16_t nItemOuterW, nItemOuterH;
// Convert linear count into row & column
nItemIndY = nDispInd / nCols; // Round down
nItemIndX = nDispInd % nCols;
// Calculate total spacing between items (including gap)
nItemOuterW = pListbox->nItemW + pListbox->nItemGap;
nItemOuterH = pListbox->nItemH + pListbox->nItemGap;
// Determine top-left corner of each item
nItemW = pListbox->nItemW;
nItemH = pListbox->nItemH;
nItemY = nItemBaseY + (nItemIndY * nItemOuterH);
nItemX = nItemBaseX + (nItemIndX * nItemOuterW);
// Create rect for item
rItemRect = (gslc_tsRect) { nItemX, nItemY, nItemW, nItemH };
// Is the item selected?
bItemSel = (nItemInd == nItemCurSel) ? true : false;
// Determine the color based on state
colFill = (bItemSel) ? pElem->colElemFillGlow : pElem->colElemFill;
colTxt = (bItemSel) ? pElem->colElemTextGlow : pElem->colElemText;
bool bDoRedraw = false;
if (eRedraw == GSLC_REDRAW_FULL) {
bDoRedraw = true;
} else if (eRedraw == GSLC_REDRAW_INC) {
// Redraw both the old selected item (ie. to unselect it)
// and the current selected item (ie. to select it)
if (nItemInd == pListbox->nItemCurSelLast) {
bDoRedraw = true;
} else if (nItemInd == nItemCurSel) {
bDoRedraw = true;
}
}
// Draw the list item
if (bDoRedraw) {
gslc_DrawFillRect(pGui, rItemRect, colFill);
// Set the text flags to indicate that the user has separately
// allocated memory for the text strings.
gslc_teTxtFlags eTxtFlags = GSLC_TXT_MEM_RAM | GSLC_TXT_ALLOC_EXT;
// Draw the aligned text string (by default it is GSLC_ALIGN_MID_LEFT)
gslc_DrawTxtBase(pGui, acStr, rItemRect, pElem->pTxtFont, eTxtFlags,
pElem->eTxtAlign, colTxt, colFill, pElem->nTxtMarginX, pElem->nTxtMarginY);
}
}
// Save the last selected item during redraw
pListbox->nItemCurSelLast = nItemCurSel;
// Clear the redraw flag
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_NONE);
// Mark page as needing flip
gslc_PageFlipSet(pGui,true);
return true;
}
bool gslc_ElemXListboxTouch(void* pvGui, void* pvElemRef, gslc_teTouch eTouch, int16_t nRelX, int16_t nRelY)
{
#if defined(DRV_TOUCH_NONE)
return false;
#else
gslc_tsGui* pGui = NULL;
gslc_tsElemRef* pElemRef = NULL;
// Typecast the parameters to match the GUI
pGui = (gslc_tsGui*)(pvGui);
pElemRef = (gslc_tsElemRef*)(pvElemRef);
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return false;
//bool bGlowingOld = gslc_ElemGetGlow(pGui, pElemRef);
bool bIndexed = false;
int16_t nItemSavedSel = pListbox->nItemSavedSel;
int16_t nItemCurSelOld = pListbox->nItemCurSel;
int16_t nItemCurSel = XLISTBOX_SEL_NONE;
bool bSelTrack = false;
bool bSelSave = false;
bool bSelInItem = true;
switch (eTouch) {
case GSLC_TOUCH_DOWN_IN:
// Start glowing as must be over it
gslc_ElemSetGlow(pGui, pElemRef, true);
// User pressed inside elem: start selection
bSelTrack = true;
break;
case GSLC_TOUCH_MOVE_IN:
gslc_ElemSetGlow(pGui, pElemRef, true);
// Track changes in selection
bSelTrack = true;
break;
case GSLC_TOUCH_MOVE_OUT:
gslc_ElemSetGlow(pGui, pElemRef, false);
// User has dragged to outside elem: deselect
bSelTrack = true;
break;
case GSLC_TOUCH_UP_IN:
// End glow
gslc_ElemSetGlow(pGui, pElemRef, false);
// User released inside elem.
// Save selection.
// If selection is same as previous: toggle it
bSelTrack = true;
bSelSave = true;
break;
case GSLC_TOUCH_UP_OUT:
// End glow
gslc_ElemSetGlow(pGui, pElemRef, false);
// User released outside elem: leave selection as-is
bSelTrack = true;
bSelSave = true; // Save SEL_NONE
break;
case GSLC_TOUCH_SET_REL:
case GSLC_TOUCH_SET_ABS:
bIndexed = true;
gslc_ElemSetGlow(pGui,pElemRef,true);
// Keyboard / pin control
bSelTrack = true;
bSelSave = true;
break;
default:
return false;
break;
}
uint8_t nCols = pListbox->nCols;
uint8_t nRows = pListbox->nRows;
// If we need to update the Listbox selection, calculate the value
// and perform the update
if (bSelTrack) {
if (bIndexed) {
// The selection is changed by direct control (eg. keyboard)
// instead of touch coordinates
// FIXME: Change the following to be either absolute or relative
// value assignment instead of inc/dec. Then the user code can
// define what the magnitude and direction should be.
if (eTouch == GSLC_TOUCH_SET_REL) {
// Overload the "nRelY" parameter as an increment value
nItemCurSel = nItemCurSelOld + nRelY;
}
else if (eTouch == GSLC_TOUCH_SET_ABS) {
// Overload the "nRelY" parameter as an absolute value
nItemCurSel = nRelY;
}
gslc_ElemXListboxSetSel(pGui, pElemRef, nItemCurSel);
}
else {
// Determine which item we are tracking
int16_t nItemOuterW, nItemOuterH;
int16_t nDispR, nDispC;
int16_t nItemR, nItemC;
// Get position relative to top-left list matrix cell
nRelX -= pListbox->nMarginW;
nRelY -= pListbox->nMarginH;
// Determine spacing between matrix cells
nItemOuterW = pListbox->nItemW + pListbox->nItemGap;
nItemOuterH = pListbox->nItemH + pListbox->nItemGap;
// Determine which matrix cell we are in
nDispC = nRelX / nItemOuterW;
nDispR = nRelY / nItemOuterH;
if ((nRelX < 0) || (nRelY < 0)) {
bSelInItem = false;
}
// Determine if the selection was inside the range of displayed items
if ((nDispR < 0) || (nDispR >= nRows) || (nDispC < 0) || (nDispC >= nCols)) {
bSelInItem = false;
}
if (bSelInItem) {
// We have confirmed that the selected cell is
// within the visible display range. Now translate
// the display cell index to the absolute list cell index
// by taking into account the scroll position.
// - Note that nItemTop is always pointing to an
// item index at the start of a row
nItemC = nDispC;
nItemR = pListbox->nItemTop + nDispR;
// Now we have identified the cell within the list matrix
nItemCurSel = nItemR * nCols + nItemC;
// Confirm we haven't selected out of range
if (nItemCurSel >= pListbox->nItemCnt) {
bSelInItem = false;
}
}
if (bSelInItem) {
// Now check for touch in gap region
// - First find offset from top-left of matrix cell
nRelX = nRelX % nItemOuterW;
nRelY = nRelY % nItemOuterH;
// If we are in the gap region, disable
if (nRelX > pListbox->nItemW) {
bSelInItem = false;
}
if (nRelY > pListbox->nItemH) {
bSelInItem = false;
}
}
// If we determined that the coordinate was not inside an
// element, then clear current selection
if (!bSelInItem) {
nItemCurSel = XLISTBOX_SEL_NONE;
}
// If we have committed a touch press within an item
// that was already selected, then toggle (deselect it)
if ((bSelSave) && (nItemCurSel == nItemSavedSel)) {
nItemCurSel = XLISTBOX_SEL_NONE;
}
bool bDoRedraw = false;
if (nItemCurSel != nItemCurSelOld) {
// Selection changed, so we will redraw
bDoRedraw = true;
}
if (bDoRedraw) {
// Update the selection
gslc_ElemXListboxSetSel(pGui, pElemRef, nItemCurSel);
// If any selection callback is defined, call it now
if (pListbox->pfuncXSel != NULL) {
(*pListbox->pfuncXSel)((void*)(pGui), (void*)(pElemRef), nItemCurSel);
}
// Redraw the element
// - Note that ElemXListboxSetSel() above will also request redraw
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_INC);
}
// Update the saved selection
if (bSelSave) {
pListbox->nItemSavedSel = nItemCurSel;
}
} // bIndexed
} // bSelTrack
return true;
#endif // DRV_TOUCH_NONE
}
int16_t gslc_ElemXListboxGetSel(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return XLISTBOX_SEL_NONE;
return pListbox->nItemCurSel;
}
bool gslc_ElemXListboxSetSel(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef, int16_t nItemCurSel)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return false;
bool bOk = false;
if (nItemCurSel == XLISTBOX_SEL_NONE) { bOk = true; }
if ((nItemCurSel >= 0) && (nItemCurSel < pListbox->nItemCnt)) { bOk = true; }
if (bOk) {
pListbox->nItemCurSel = nItemCurSel;
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_INC);
}
return bOk;
}
bool gslc_ElemXListboxSetScrollPos(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, uint16_t nScrollPos)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return false;
bool bOk = false;
if ((nScrollPos >= 0) && (nScrollPos < pListbox->nItemCnt)) {
bOk = true;
}
int16_t nCols = pListbox->nCols;
int16_t nItemCnt = pListbox->nItemCnt;
// Error handling: in case position out of bounds
if (nScrollPos >= nItemCnt) {
nScrollPos = (nItemCnt > 0) ? nItemCnt - 1 : 0;
}
// Adjust the top item index to the start of its row
// - This is done because we may have multiple columns
// per row. This ensures nItemTop points to the list
// index at the start of a row.
nScrollPos = (nScrollPos / nCols) * nCols;
pListbox->nItemTop = nScrollPos;
// Need to update all rows in display
gslc_ElemSetRedraw(pGui, pElemRef, GSLC_REDRAW_FULL);
return bOk;
}
// Assign the selection callback function for a Listbox
void gslc_ElemXListboxSetSelFunc(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,GSLC_CB_XLISTBOX_SEL funcCb)
{
gslc_tsXListbox* pListbox = (gslc_tsXListbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_LISTBOX, __LINE__);
if (!pListbox) return;
pListbox->pfuncXSel = funcCb;
}
// ============================================================================