// ======================================================================= // 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 XCheckbox.c // GUIslice library #include "GUIslice.h" #include "GUIslice_drv.h" #include "XCheckbox.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 // // ---------------------------------------------------------------------------- // ============================================================================ // Extended Element: Checkbox // - Checkbox with custom handler for touch tracking which // enables glow to be defined whenever touch is tracked over // the element. // ============================================================================ // Create a checkbox element and add it to the GUI element list // - Defines default styling for the element // - Defines callback for redraw but does not track touch/click gslc_tsElemRef* gslc_ElemXCheckboxCreate(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage, gslc_tsXCheckbox* pXData,gslc_tsRect rElem,bool bRadio,gslc_teXCheckboxStyle nStyle, gslc_tsColor colCheck,bool bChecked) { if ((pGui == NULL) || (pXData == NULL)) { static const char GSLC_PMEM FUNCSTR[] = "ElemXCheckboxCreate"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return NULL; } gslc_tsElem sElem; gslc_tsElemRef* pElemRef = NULL; sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPEX_CHECKBOX,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; sElem.nFeatures |= GSLC_ELEM_FEA_GLOW_EN; // Default group assignment. Can override later with ElemSetGroup() sElem.nGroup = GSLC_GROUP_ID_NONE; // Define other extended data pXData->bRadio = bRadio; pXData->bChecked = bChecked; pXData->colCheck = colCheck; pXData->nStyle = nStyle; pXData->pfuncXToggle = NULL; sElem.pXData = (void*)(pXData); // Specify the custom drawing callback sElem.pfuncXDraw = &gslc_ElemXCheckboxDraw; // Specify the custom touch tracking callback // - NOTE: This is optional (and can be set to NULL). // See the discussion under gslc_ElemXCheckboxTouch() sElem.pfuncXTouch = &gslc_ElemXCheckboxTouch; sElem.colElemFill = GSLC_COL_BLACK; sElem.colElemFillGlow = GSLC_COL_BLACK; sElem.colElemFrame = GSLC_COL_GRAY; sElem.colElemFrameGlow = GSLC_COL_WHITE; 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; } bool gslc_ElemXCheckboxGetState(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { gslc_tsXCheckbox* pCheckbox = (gslc_tsXCheckbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_CHECKBOX, __LINE__); if (!pCheckbox) return false; return pCheckbox->bChecked; } // Determine which checkbox in the group has been "checked" gslc_tsElemRef* gslc_ElemXCheckboxFindChecked(gslc_tsGui* pGui,int16_t nGroupId) { int16_t nCurInd; gslc_tsElemRef* pCurElemRef = NULL; gslc_tsElem* pCurElem = NULL; int16_t nCurType; int16_t nCurGroup; bool bCurChecked; gslc_tsElemRef* pFoundElemRef = NULL; // Operate on current page // TODO: Support other page layers gslc_tsPage* pPage = pGui->apPageStack[GSLC_STACK_CUR]; if (pPage == NULL) { return NULL; // No page added yet } if (pGui == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemXCheckboxFindChecked"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return NULL; } gslc_tsCollect* pCollect = &pPage->sCollect; for (nCurInd=0;nCurIndnElemCnt;nCurInd++) { // Fetch extended data pCurElemRef = &(pCollect->asElemRef[nCurInd]); pCurElem = gslc_GetElemFromRef(pGui,pCurElemRef); nCurType = pCurElem->nType; // Only want to proceed if it is a checkbox if (nCurType != GSLC_TYPEX_CHECKBOX) { continue; } nCurGroup = pCurElem->nGroup; bCurChecked = gslc_ElemXCheckboxGetState(pGui,pCurElemRef); // If this is in a different group, ignore it if (nCurGroup != nGroupId) { continue; } // Did we find an element in the group that was checked? if (bCurChecked) { pFoundElemRef = pCurElemRef; break; } } // nCurInd return pFoundElemRef; } // Assign the callback function for checkbox/radio state change events void gslc_ElemXCheckboxSetStateFunc(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, GSLC_CB_XCHECKBOX pfuncCb) { gslc_tsXCheckbox* pCheckbox = (gslc_tsXCheckbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_CHECKBOX, __LINE__); if (!pCheckbox) return; pCheckbox->pfuncXToggle = pfuncCb; } // Helper routine for gslc_ElemXCheckboxSetState() // - Updates the checkbox/radio control's state but does // not touch any other controls in the group void gslc_ElemXCheckboxSetStateHelp(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bChecked,bool bDoCb) { gslc_tsXCheckbox* pCheckbox = (gslc_tsXCheckbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_CHECKBOX, __LINE__); if (!pCheckbox) return; gslc_tsElem* pElem = gslc_GetElemFromRef(pGui,pElemRef); // Update our data element bool const bCheckedOld = pCheckbox->bChecked; pCheckbox->bChecked = bChecked; // Element needs redraw if (bChecked != bCheckedOld) { // Only need an incremental redraw gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_INC); } else { // Same state as before // - No need to do anything further since we don't need to // trigger any callbacks or redraw return; } // If no callbacks requested, exit now if (!bDoCb) { return; } // If any state callback is defined, call it now // - In all cases, return the ElementRef of the element that issued the callback // // For checkbox: // - If not selected: return current ID and state=false // - If selected: return current ID and state=true // For radio button: // - If none selected: return ID_NONE and state=false // - If one selected: return selected ID and state=true if (pCheckbox->pfuncXToggle != NULL) { gslc_tsElemRef* pRetRef = NULL; int16_t nGroup = GSLC_GROUP_ID_NONE; int16_t nSelId = GSLC_ID_NONE; if (!pCheckbox->bRadio) { // Checkbox nSelId = pElem->nId; } else { // Radio button // - Determine the group that the radio button belongs to nGroup = pElem->nGroup; // Determine if any radio button in the group has been selected pRetRef = gslc_ElemXCheckboxFindChecked(pGui, nGroup); if (pRetRef != NULL) { // One has been selected, return its ID bChecked = true; nSelId = pRetRef->pElem->nId; } else { // No radio button selected, return ID NONE bChecked = false; nSelId = GSLC_ID_NONE; } } // Now send the callback notification (*pCheckbox->pfuncXToggle)((void*)(pGui), (void*)(pElemRef), nSelId, bChecked); } // pfuncXToggle } // Update the checkbox/radio control's state. If it is a radio button // then also update the state of all other buttons in the group. void gslc_ElemXCheckboxSetState(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bChecked) { gslc_tsXCheckbox* pCheckbox = (gslc_tsXCheckbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_CHECKBOX, __LINE__); if (!pCheckbox) return; gslc_tsElem* pElem = gslc_GetElemFromRef(pGui,pElemRef); gslc_tsPage* pPage = NULL; bool bRadio = pCheckbox->bRadio; int16_t nGroup = pElem->nGroup; int16_t nElemId = pElem->nId; // Special handling when we select a radio button if (bRadio && bChecked) { // If we are selecting a radio button that is already // selected, then skip further update events. // NOTE: This check is not very efficient, but it avoids // the creation of extra events. gslc_tsElemRef* pTmpRef = gslc_ElemXCheckboxFindChecked(pGui, nGroup); if (pTmpRef == pElemRef) { // Same element, so skip return; } // Proceed to deselect any other selected items in the group. // Note that SetState calls itself to deselect other items so it // is important to qualify this logic with bChecked=true int16_t nCurInd; int16_t nCurId; gslc_tsElem* pCurElem = NULL; gslc_tsElemRef* pCurElemRef = NULL; int16_t nCurType; int16_t nCurGroup; // We use the GUI pointer for access to other elements // Check all pages in case we are affecting radio buttons on other pages for (int8_t nPageInd=0;nPageIndnPageCnt;nPageInd++) { pPage = &pGui->asPage[nPageInd]; if (!pPage) { // If this stack page is not enabled, skip to next stack page continue; } gslc_tsCollect* pCollect = &pPage->sCollect; for (nCurInd=0;nCurIndnElemRefCnt;nCurInd++) { // Fetch extended data pCurElemRef = &pCollect->asElemRef[nCurInd]; pCurElem = gslc_GetElemFromRef(pGui,pCurElemRef); // FIXME: Handle pCurElemRef->eElemFlags nCurId = pCurElem->nId; nCurType = pCurElem->nType; // Only want to proceed if it is a checkbox if (nCurType != GSLC_TYPEX_CHECKBOX) { continue; } nCurGroup = pCurElem->nGroup; // If this is in a different group, ignore it if (nCurGroup != nGroup) { continue; } // Is this our element? If so, ignore the deselect operation if (nCurId == nElemId) { continue; } // Deselect all other elements // - But don't trigger any callbacks gslc_ElemXCheckboxSetStateHelp(pGui,pCurElemRef,false,false); } // nInd } // nStackPage } // bRadio // Set the state of the current element // - Trigger callback if enabled gslc_ElemXCheckboxSetStateHelp(pGui,pElemRef,bChecked,true); } // Toggle the checkbox control's state void gslc_ElemXCheckboxToggleState(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef) { if (pElemRef == NULL) { static const char GSLC_PMEM FUNCSTR[] = "ElemXCheckboxToggleState"; GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR); return; } // Update the data element bool bCheckNew = (gslc_ElemXCheckboxGetState(pGui,pElemRef))? false : true; gslc_ElemXCheckboxSetState(pGui,pElemRef,bCheckNew); // Element needs redraw // - Only incremental is needed gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_INC); } // Redraw the checkbox // - 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_ElemXCheckboxDraw(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_tsXCheckbox* pCheckbox = (gslc_tsXCheckbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_CHECKBOX, __LINE__); if (!pCheckbox) return false; gslc_tsElem* pElem = gslc_GetElemFromRef(pGui,pElemRef); gslc_tsRect rInner; bool bChecked = pCheckbox->bChecked; gslc_teXCheckboxStyle nStyle = pCheckbox->nStyle; bool bGlow = (pElem->nFeatures & GSLC_ELEM_FEA_GLOW_EN) && gslc_ElemGetGlow(pGui,pElemRef); // Draw the background gslc_DrawFillRect(pGui,pElem->rElem,pElem->colElemFill); // Generic coordinate calcs int16_t nX0,nY0,nX1,nY1,nMidX,nMidY; nX0 = pElem->rElem.x; nY0 = pElem->rElem.y; nX1 = pElem->rElem.x + pElem->rElem.w - 1; nY1 = pElem->rElem.y + pElem->rElem.h - 1; nMidX = (nX0+nX1)/2; nMidY = (nY0+nY1)/2; if (nStyle == GSLCX_CHECKBOX_STYLE_BOX) { // Draw the center indicator if checked rInner = gslc_ExpandRect(pElem->rElem,-5,-5); if (bChecked) { // If checked, fill in the inner region gslc_DrawFillRect(pGui,rInner,pCheckbox->colCheck); } else { // Assume the background fill has already been done so // we don't need to do anything more in the unchecked case } // Draw a frame around the checkbox gslc_DrawFrameRect(pGui,pElem->rElem,(bGlow)?pElem->colElemFrameGlow:pElem->colElemFrame); } else if (nStyle == GSLCX_CHECKBOX_STYLE_X) { // Draw an X through center if checked if (bChecked) { gslc_DrawLine(pGui,nX0,nY0,nX1,nY1,pCheckbox->colCheck); gslc_DrawLine(pGui,nX0,nY1,nX1,nY0,pCheckbox->colCheck); } // Draw a frame around the checkbox gslc_DrawFrameRect(pGui,pElem->rElem,(bGlow)?pElem->colElemFrameGlow:pElem->colElemFrame); } else if (nStyle == GSLCX_CHECKBOX_STYLE_ROUND) { // Draw inner circle if checked if (bChecked) { gslc_DrawFillCircle(pGui,nMidX,nMidY,5,pCheckbox->colCheck); } // Draw a frame around the checkbox gslc_DrawFrameCircle(pGui,nMidX,nMidY,(pElem->rElem.w/2),(bGlow)?pElem->colElemFrameGlow:pElem->colElemFrame); } // Clear the redraw flag gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_NONE); // Mark page as needing flip gslc_PageFlipSet(pGui,true); return true; } // This callback function is called by gslc_ElemSendEventTouch() // after any touch event // - NOTE: Adding this touch callback is optional. Without it, we // can still have a functional checkbox, but doing the touch // tracking allows us to change the glow state of the element // dynamically, as well as updating the checkbox state if the // user releases over it (ie. a click event). // bool gslc_ElemXCheckboxTouch(void* pvGui,void* pvElemRef,gslc_teTouch eTouch,int16_t nRelX,int16_t nRelY) { #if defined(DRV_TOUCH_NONE) return false; #else // Typecast the parameters to match the GUI and element types gslc_tsGui* pGui = (gslc_tsGui*)(pvGui); gslc_tsElemRef* pElemRef = (gslc_tsElemRef*)(pvElemRef); gslc_tsXCheckbox* pCheckbox = (gslc_tsXCheckbox*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_CHECKBOX, __LINE__); if (!pCheckbox) return false; //gslc_tsElem* pElem = gslc_GetElemFromRef(pGui,pElemRef); bool bRadio = pCheckbox->bRadio; bool bCheckedOld = pCheckbox->bChecked; bool bGlowingOld = gslc_ElemGetGlow(pGui,pElemRef); switch(eTouch) { case GSLC_TOUCH_DOWN_IN: gslc_ElemSetGlow(pGui,pElemRef,true); break; case GSLC_TOUCH_MOVE_IN: gslc_ElemSetGlow(pGui,pElemRef,true); break; case GSLC_TOUCH_MOVE_OUT: gslc_ElemSetGlow(pGui,pElemRef,false); break; case GSLC_TOUCH_UP_IN: gslc_ElemSetGlow(pGui,pElemRef,false); // Now that we released on element, update the state bool bCheckNew; if (bRadio) { // Radio button action: set bCheckNew = true; } else { // Checkbox button action: toggle bCheckNew = (pCheckbox->bChecked)?false:true; } gslc_ElemXCheckboxSetState(pGui,pElemRef,bCheckNew); break; case GSLC_TOUCH_UP_OUT: gslc_ElemSetGlow(pGui,pElemRef,false); break; default: return false; break; } // If the checkbox changed state, redraw bool bChanged = false; if (gslc_ElemGetGlow(pGui,pElemRef) != bGlowingOld) { bChanged = true; } if (pCheckbox->bChecked != bCheckedOld) { bChanged = true; } if (bChanged) { // Incremental redraw gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_INC); } return true; #endif // !DRV_TOUCH_NONE } // ============================================================================