// ======================================================================= // 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 #if (GSLC_USE_PROGMEM) #include #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; } // ============================================================================