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

454 lines
16 KiB
C
Raw Normal View History

// =======================================================================
// GUIslice library extension: Toggle button control
// - Paul Conti
// - Toggle button with Android and iOS like styles
// - Based on Calvin Hass' Checkbox control
// - https://www.impulseadventure.com/elec/guislice-gui.html
// - https://github.com/ImpulseAdventure/GUIslice
// =======================================================================
//
// The MIT License
//
// Copyright 2016-2020 Calvin Hass and Paul Conti
//
// 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 XTogglebtn.c
// GUIslice library
#include "GUIslice.h"
#include "GUIslice_drv.h"
//#include "XTogglebtn.h"
#include "XTogglebtn.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
//
// ----------------------------------------------------------------------------
// ============================================================================
// Extended Element: Togglebtn
// - Togglebtn
// Acts much like a checkbox but with styles that are similar to iOS and
// Android slider buttons.
// ============================================================================
// Create a togglebtn element and add it to the GUI element list
gslc_tsElemRef* gslc_ElemXTogglebtnCreate(gslc_tsGui* pGui,int16_t nElemId,int16_t nPage,
gslc_tsXTogglebtn* pXData,gslc_tsRect rElem,
gslc_tsColor colThumb,gslc_tsColor colOnState,gslc_tsColor colOffState,
bool bCircular,bool bChecked,GSLC_CB_TOUCH cbTouch)
{
if ((pGui == NULL) || (pXData == NULL)) {
static const char GSLC_PMEM FUNCSTR[] = "ElemXTogglebtnCreate";
GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
return NULL;
}
gslc_tsElem sElem;
gslc_tsElemRef* pElemRef = NULL;
sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPEX_TOGGLEBTN,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;
// Define other extended data
sElem.pXData = (void*)(pXData);
pXData->bOn = bChecked; // save on/off status
pXData->pfunctUser = cbTouch; // save user's callback in our extra data
pXData->nMyPageId = nPage; // save our page id for group by access later, if needed.
pXData->colThumb = colThumb; // save thumb color
pXData->colOnState = colOnState; // save on color
pXData->colOffState = colOffState; // save off color
pXData->bCircular = bCircular; // save button style
// Specify the custom drawing callback
sElem.pfuncXDraw = &gslc_ElemXTogglebtnDraw;
// Specify the custom touch handler
sElem.pfuncXTouch = &gslc_ElemXTogglebtnTouch;
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_ElemXTogglebtnGetState(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef)
{
gslc_tsXTogglebtn* pTogglebtn = (gslc_tsXTogglebtn*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_TOGGLEBTN, __LINE__);
if (!pTogglebtn) return false;
return pTogglebtn->bOn;
}
// Helper routine for gslc_ElemXTogglebtnSetState()
// - Updates the togglebtn control's state but does
// not touch any other controls in the group
void gslc_ElemXTogglebtnSetStateHelp(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bOn)
{
gslc_tsXTogglebtn* pTogglebtn = (gslc_tsXTogglebtn*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_TOGGLEBTN, __LINE__);
if (!pTogglebtn) return;
// Update our data element
bool bStateOld = pTogglebtn->bOn;
pTogglebtn->bOn = bOn;
// Element needs redraw
if (bOn != bStateOld) {
// Only need an incremental redraw
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_INC);
}
}
// Update the togglebtn control's state. If it's part of a group
// then also update the state of all other buttons in the group.
void gslc_ElemXTogglebtnSetState(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef,bool bOn)
{
gslc_tsXTogglebtn* pTogglebtn = (gslc_tsXTogglebtn*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_TOGGLEBTN, __LINE__);
if (!pTogglebtn) return;
gslc_tsElem* pElem = gslc_GetElemFromRef(pGui,pElemRef);
int16_t nGroup = pElem->nGroup;
int16_t nElemId = pElem->nId;
if (bOn && pElem->nGroup != GSLC_GROUP_ID_NONE) {
// If we are selecting a 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_ElemXTogglebtnFindSelected(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 bOn=true
int16_t nCurInd;
int16_t nCurId;
gslc_tsElem* pCurElem = NULL;
gslc_tsElemRef* pCurElemRef = NULL;
int16_t nCurGroup;
/*
* The elements must be grouped on the same layer but do not need to be on the current
* page. This allows us to place grouped elements on the base page.
* p conti.
*/
// Find our page layer
gslc_tsPage* pPage = gslc_PageFindById(pGui, pTogglebtn->nMyPageId);
if (pPage == NULL) {
GSLC_DEBUG2_PRINT("ERROR: gslc_ElemXTogglebtnSetState() can't find page (ID=%d)\n",
pTogglebtn->nMyPageId);
return;
}
gslc_tsCollect* pCollect = &pPage->sCollect;
for (nCurInd=0;nCurInd<pCollect->nElemRefCnt;nCurInd++) {
// Fetch extended data
pCurElemRef = &pCollect->asElemRef[nCurInd];
pCurElem = gslc_GetElemFromRef(pGui,pCurElemRef);
// NOTE: Sorry but I have no idea what this FIXME is talking about - p conti
// FIXME: Handle pCurElemRef->eElemFlags
nCurId = pCurElem->nId;
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
gslc_ElemSetGlow(pGui,pCurElemRef,false); // trurn off glow state
gslc_ElemXTogglebtnSetStateHelp(pGui,pCurElemRef,false);
} // nInd
} // bOn
// Set the state of the current element
gslc_ElemXTogglebtnSetStateHelp(pGui,pElemRef,bOn);
}
// Toggle the togglebtn control's state
void gslc_ElemXTogglebtnToggleState(gslc_tsGui* pGui,gslc_tsElemRef* pElemRef)
{
if (pElemRef == NULL) {
static const char GSLC_PMEM FUNCSTR[] = "ElemXTogglebtnToggleState";
GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
return;
}
// Toggle the data element value
bool bStateNew = (gslc_ElemXTogglebtnGetState(pGui,pElemRef))? false : true;
gslc_ElemXTogglebtnSetState(pGui,pElemRef,bStateNew);
}
void gslc_ElemXTogglebtnDrawCircularHelp(gslc_tsGui* pGui,gslc_tsElem* pElem,gslc_tsXTogglebtn* pTogglebtn)
{
// frame enabled?
bool bFrameEn = pElem->nFeatures & GSLC_ELEM_FEA_FRAME_EN;
// Work out the sizes of the inner rectangles
gslc_tsRect rInner = gslc_ExpandRect(pElem->rElem,-1,-1);
// work out our circle positions
uint16_t nRadius = rInner.h / 2;
int16_t nLeftX = rInner.x + nRadius;
int16_t nLeftY = rInner.y + nRadius;
int16_t nRightX = rInner.x + pElem->rElem.w - nRadius -1;
int16_t nRightY = rInner.y + nRadius;
if (pTogglebtn->bOn) {
// draw our main body
gslc_DrawFillRoundRect(pGui,rInner,rInner.h,pTogglebtn->colOnState);
// place thumb on left-hand side
gslc_DrawFillCircle(pGui,nLeftX,nLeftY,nRadius-1,pTogglebtn->colThumb);
if (bFrameEn) {
gslc_DrawFrameRoundRect(pGui,pElem->rElem,pElem->rElem.h,pElem->colElemFrame);
gslc_DrawFrameCircle(pGui,nLeftX,nLeftY,nRadius,pElem->colElemFrame);
}
} else {
// draw our main body
gslc_DrawFillRoundRect(pGui,rInner,rInner.h,pTogglebtn->colOffState);
// place thumb on right-hand side
gslc_DrawFillCircle(pGui,nRightX,nRightY,nRadius-1,pTogglebtn->colThumb);
if (bFrameEn) {
gslc_DrawFrameRoundRect(pGui,pElem->rElem,pElem->rElem.h,pElem->colElemFrame);
gslc_DrawFrameCircle(pGui,nRightX,nRightY,nRadius,pElem->colElemFrame);
}
}
}
void gslc_ElemXTogglebtnDrawRectangularHelp(gslc_tsGui* pGui,gslc_tsElem* pElem,gslc_tsXTogglebtn* pTogglebtn)
{
// frame enabled?
bool bFrameEn = pElem->nFeatures & GSLC_ELEM_FEA_FRAME_EN;
// Work out the sizes of the inner rectangles
gslc_tsRect rSquare = {
pElem->rElem.x,
pElem->rElem.y,
pElem->rElem.h, // force a square
pElem->rElem.h
};
gslc_tsRect rInner = gslc_ExpandRect(pElem->rElem,-1,-1);
if (pTogglebtn->bOn) {
gslc_DrawFillRect(pGui,rInner,pTogglebtn->colOnState);
// place thumb on left-hand side
gslc_DrawFillRect(pGui,rSquare,pTogglebtn->colThumb);
if (bFrameEn) {
gslc_DrawFrameRect(pGui,pElem->rElem,pElem->colElemFrame);
gslc_DrawFrameRect(pGui,rSquare,pElem->colElemFrame);
}
} else {
gslc_DrawFillRect(pGui,rInner,pTogglebtn->colOffState);
// place thumb on right-hand side
rSquare.x = rInner.x + rInner.w - rInner.h - 1;
gslc_DrawFillRect(pGui,rSquare,pTogglebtn->colThumb);
if (bFrameEn) {
gslc_DrawFrameRect(pGui,pElem->rElem,pElem->colElemFrame);
gslc_DrawFrameRect(pGui,rSquare,pElem->colElemFrame);
}
}
}
// Redraw the togglebtn
// - 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_ElemXTogglebtnDraw(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_tsXTogglebtn* pTogglebtn = (gslc_tsXTogglebtn*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_TOGGLEBTN, __LINE__);
if (!pTogglebtn) {
GSLC_DEBUG_PRINT("ERROR: gslc_ElemXTogglebtnDraw(%s) pXData is NULL\n","");
return false;
}
gslc_tsElem* pElem = gslc_GetElemFromRef(pGui,pElemRef);
if (pTogglebtn->bCircular) {
gslc_ElemXTogglebtnDrawCircularHelp(pGui, pElem, pTogglebtn);
} else {
gslc_ElemXTogglebtnDrawRectangularHelp(pGui, pElem, pTogglebtn);
}
// 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 togglebtn, but doing the touch
// tracking allows us to change the glow state of the element
// dynamically, as well as updating the togglebtn state if the
// user releases over it (ie. a click event).
//
bool gslc_ElemXTogglebtnTouch(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_tsXTogglebtn* pTogglebtn = (gslc_tsXTogglebtn*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_TOGGLEBTN, __LINE__);
if (!pTogglebtn) return false;
//gslc_tsElem* pElem = gslc_GetElemFromRef(pGui,pElemRef);
bool bStateOld = pTogglebtn->bOn;
switch(eTouch) {
case GSLC_TOUCH_UP_IN:
// Now that we released on element, update the state
// Togglebtn button action: toggle
gslc_ElemXTogglebtnToggleState(pGui,pElemRef);
break;
default:
return false;
break;
}
// If the togglebtn changed state, redraw and notify user
if (pTogglebtn->bOn != bStateOld) {
// Now send the callback notification
(*pTogglebtn->pfunctUser)((void*)(pGui), (void*)(pElemRef), eTouch, nRelX, nRelY);
// Incremental redraw
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_INC);
}
return true;
#endif // !DRV_TOUCH_NONE
}
// Determine which togglebtn in the group is selected "on"
gslc_tsElemRef* gslc_ElemXTogglebtnFindSelected(gslc_tsGui* pGui,int16_t nGroupId)
{
int16_t nCurInd;
gslc_tsElemRef* pCurElemRef = NULL;
gslc_tsElem* pCurElem = NULL;
int16_t nCurType;
int16_t nCurGroup;
bool bCurSelected;
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[] = "ElemXTogglebtnFindChecked";
GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
return NULL;
}
gslc_tsCollect* pCollect = &pPage->sCollect;
for (nCurInd=0;nCurInd<pCollect->nElemCnt;nCurInd++) {
// Fetch extended data
pCurElemRef = &(pCollect->asElemRef[nCurInd]);
pCurElem = gslc_GetElemFromRef(pGui,pCurElemRef);
nCurType = pCurElem->nType;
// Only want to proceed if it is a togglebtn
if (nCurType != GSLC_TYPEX_TOGGLEBTN) {
continue;
}
nCurGroup = pCurElem->nGroup;
bCurSelected = gslc_ElemXTogglebtnGetState(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 (bCurSelected) {
pFoundElemRef = pCurElemRef;
break;
}
} // nCurInd
return pFoundElemRef;
}
// ============================================================================