414 lines
15 KiB
C
414 lines
15 KiB
C
|
// =======================================================================
|
||
|
// GUIslice library (Ring control)
|
||
|
// - 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 XRingGauge.c
|
||
|
|
||
|
|
||
|
|
||
|
// GUIslice library
|
||
|
#include "GUIslice.h"
|
||
|
#include "GUIslice_drv.h"
|
||
|
|
||
|
#include "XRingGauge.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: Ring gauge
|
||
|
// ============================================================================
|
||
|
|
||
|
// Create a text element and add it to the GUI element list
|
||
|
// - Defines default styling for the element
|
||
|
// - Defines callback for redraw and touch
|
||
|
gslc_tsElemRef* gslc_ElemXRingGaugeCreate(gslc_tsGui* pGui, int16_t nElemId, int16_t nPage,
|
||
|
gslc_tsXRingGauge* pXData, gslc_tsRect rElem, char* pStrBuf, uint8_t nStrBufMax, int16_t nFontId)
|
||
|
{
|
||
|
if ((pGui == NULL) || (pXData == NULL)) {
|
||
|
static const char GSLC_PMEM FUNCSTR[] = "ElemXRingGaugeCreate";
|
||
|
GSLC_DEBUG2_PRINT_CONST(ERRSTR_NULL,FUNCSTR);
|
||
|
return NULL;
|
||
|
}
|
||
|
gslc_tsElem sElem;
|
||
|
gslc_tsElemRef* pElemRef = NULL;
|
||
|
sElem = gslc_ElemCreate(pGui,nElemId,nPage,GSLC_TYPEX_RING,rElem,pStrBuf,nStrBufMax,nFontId);
|
||
|
sElem.colElemFill = GSLC_COL_BLACK;
|
||
|
sElem.colElemFrame = GSLC_COL_BLUE;
|
||
|
sElem.colElemText = GSLC_COL_YELLOW;
|
||
|
sElem.nFeatures = GSLC_ELEM_FEA_FILL_EN;
|
||
|
sElem.nFeatures &= ~GSLC_ELEM_FEA_FRAME_EN;
|
||
|
sElem.nFeatures &= ~GSLC_ELEM_FEA_CLICK_EN;
|
||
|
sElem.nFeatures &= ~GSLC_ELEM_FEA_GLOW_EN;
|
||
|
sElem.eTxtAlign = GSLC_ALIGN_MID_MID;
|
||
|
|
||
|
sElem.nGroup = GSLC_GROUP_ID_NONE;
|
||
|
|
||
|
// Provide default config
|
||
|
pXData->nValMin = 0;
|
||
|
pXData->nValMax = 100;
|
||
|
pXData->nAngStart = 0;
|
||
|
pXData->nAngRange = 360;
|
||
|
pXData->nThickness = 10;
|
||
|
|
||
|
pXData->nQuality = 72; // 360/72=5 degree segments
|
||
|
|
||
|
pXData->bGradient = false;
|
||
|
pXData->nSegGap = 0;
|
||
|
pXData->colRing1 = GSLC_COL_BLUE_LT4;
|
||
|
pXData->colRing2 = GSLC_COL_RED;
|
||
|
pXData->colRingRemain = (gslc_tsColor) { 0, 0, 48 };
|
||
|
|
||
|
pXData->nVal = 0;
|
||
|
pXData->nValLast = 0;
|
||
|
pXData->acStrLast[0] = 0;
|
||
|
|
||
|
|
||
|
sElem.pXData = (void*)(pXData);
|
||
|
// Specify the custom drawing callback
|
||
|
sElem.pfuncXDraw = &gslc_ElemXRingGaugeDraw;
|
||
|
// Specify the custom touch tracking callback
|
||
|
sElem.pfuncXTouch = NULL;
|
||
|
|
||
|
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 element
|
||
|
// - 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_ElemXRingGaugeDraw(void* pvGui, void* pvElemRef, gslc_teRedrawType eRedraw)
|
||
|
{
|
||
|
gslc_tsGui* pGui = (gslc_tsGui*)pvGui;
|
||
|
gslc_tsElemRef* pElemRef = (gslc_tsElemRef*)pvElemRef;
|
||
|
gslc_tsElem* pElem = gslc_GetElemFromRefD(pGui, pElemRef, __LINE__);
|
||
|
gslc_tsXRingGauge* pXRingGauge = (gslc_tsXRingGauge*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_RING, __LINE__);
|
||
|
if (!pXRingGauge) return false;
|
||
|
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// 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
|
||
|
|
||
|
int16_t nVal = pXRingGauge->nVal;
|
||
|
int16_t nValLast = pXRingGauge->nValLast;
|
||
|
int16_t nValMin = pXRingGauge->nValMin;
|
||
|
int16_t nValMax = pXRingGauge->nValMax;
|
||
|
int16_t nValRange = (nValMax == nValMin)? 1 : (nValMax - nValMin); // Guard against div/0
|
||
|
int16_t nAngStart = pXRingGauge->nAngStart;
|
||
|
int16_t nAngRange = pXRingGauge->nAngRange;
|
||
|
//int16_t nAngEnd = nAngStart + nAngRange;
|
||
|
int16_t nQuality = pXRingGauge->nQuality;
|
||
|
|
||
|
gslc_tsColor colRingActive1 = pXRingGauge->colRing1;
|
||
|
gslc_tsColor colRingActive2 = pXRingGauge->colRing2;
|
||
|
gslc_tsColor colRingInactive = pXRingGauge->colRingRemain;
|
||
|
bool bGradient = pXRingGauge->bGradient;
|
||
|
gslc_tsColor colBg = pElem->colElemFill; // Background color used for text clearance
|
||
|
|
||
|
// Calculate the ring center and radius
|
||
|
int16_t nMidX = nElemX + nElemW / 2;
|
||
|
int16_t nMidY = nElemY + nElemH / 2;
|
||
|
int16_t nRad2 = (nElemW < nElemH) ? nElemW / 2 : nElemH / 2;
|
||
|
int16_t nRad1 = nRad2 - pXRingGauge->nThickness;
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
// Determine whether we should draw the full range (full redraw)
|
||
|
// or a smaller, updated region (incremental redraw)
|
||
|
bool bInc = (eRedraw == GSLC_REDRAW_INC) ? true : false;
|
||
|
|
||
|
int16_t nDrawStart = 0;
|
||
|
int16_t nDrawVal = 0;
|
||
|
int16_t nDrawEnd = 0;
|
||
|
|
||
|
bool bDrawActive = false;
|
||
|
bool bDrawInactive = false;
|
||
|
if (bInc) {
|
||
|
if (nVal > nValLast) {
|
||
|
// Incremental redraw: adding value, so draw with active color
|
||
|
bDrawActive = true;
|
||
|
nDrawStart = (int32_t)(nValLast - nValMin) * nAngRange / nValRange;
|
||
|
nDrawVal = (int32_t)(nVal - nValMin) * nAngRange / nValRange;
|
||
|
}
|
||
|
else {
|
||
|
// Incremental redraw: reducing value, so draw with inactive color
|
||
|
bDrawInactive = true;
|
||
|
nDrawVal = (int32_t)(nVal - nValMin) * nAngRange / nValRange;
|
||
|
nDrawEnd = (int32_t)(nValLast - nValMin) * nAngRange / nValRange;
|
||
|
}
|
||
|
} else {
|
||
|
// Full redraw: draw both active and inactive regions
|
||
|
bDrawActive = true;
|
||
|
bDrawInactive = true;
|
||
|
nDrawStart = 0;
|
||
|
nDrawVal = (int32_t)(nVal - nValMin) * nAngRange / nValRange;
|
||
|
nDrawEnd = nAngRange;
|
||
|
}
|
||
|
|
||
|
#if defined(DBG_REDRAW)
|
||
|
GSLC_DEBUG2_PRINT("\n\nRingDraw: Val=%d ValLast=%d ValRange=%d PosMin=%d PosMax=%d Q=%d\n", nVal, nValLast, nValRange, nValMin, nValMax, nQuality);
|
||
|
GSLC_DEBUG2_PRINT("RingDraw: Inc=%d ValLast=%d Val=%d Ang=%d..%d (Range=%d)\n", bInc,nValLast,nVal,nAngStart, nAngStart+nAngRange,nAngRange); //CAL!
|
||
|
GSLC_DEBUG2_PRINT("RingDraw: DrawActive=%d DrawInactive=%d DrawStart=%d DrawVal=%d DrawEnd=%d\n",bDrawActive,bDrawInactive,nDrawStart,nDrawVal,nDrawEnd);//CAL!
|
||
|
#endif
|
||
|
|
||
|
// Adjust for start of angular range
|
||
|
nDrawStart += nAngStart;
|
||
|
nDrawVal += nAngStart;
|
||
|
nDrawEnd += nAngStart;
|
||
|
|
||
|
if (bDrawActive) {
|
||
|
if (bGradient) {
|
||
|
#if defined(DBG_REDRAW)
|
||
|
GSLC_DEBUG2_PRINT("RingDraw: ActiveG start=%d end=%d astart=%d arange=%d\n", nDrawStart, nDrawVal,nAngStart,nAngRange);
|
||
|
#endif
|
||
|
gslc_DrawFillGradSector(pGui, nQuality, nMidX, nMidY,
|
||
|
nRad1, nRad2, colRingActive1, colRingActive2, nDrawStart, nDrawVal, nAngStart, nAngRange);
|
||
|
} else {
|
||
|
#if defined(DBG_REDRAW)
|
||
|
GSLC_DEBUG2_PRINT("RingDraw: Active start=%d end=%d\n", nDrawStart, nDrawVal);
|
||
|
#endif
|
||
|
gslc_DrawFillSector(pGui, nQuality, nMidX, nMidY,
|
||
|
nRad1, nRad2, colRingActive1, nDrawStart, nDrawVal);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bDrawInactive) {
|
||
|
#if defined(DBG_REDRAW)
|
||
|
GSLC_DEBUG2_PRINT("RingDraw: Inactive start=%d end=%d\n", nDrawEnd, nDrawVal);
|
||
|
#endif
|
||
|
// Since we are erasing, we will reverse the redraw direction (swap Val & End)
|
||
|
gslc_DrawFillSector(pGui, nQuality, nMidX, nMidY,
|
||
|
nRad1, nRad2, colRingInactive, nDrawEnd, nDrawVal);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// 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;
|
||
|
|
||
|
// Erase old string content using "background" color
|
||
|
if (strlen(pXRingGauge->acStrLast) != 0) {
|
||
|
gslc_DrawTxtBase(pGui, pXRingGauge->acStrLast, pElem->rElem, pElem->pTxtFont, pElem->eTxtFlags,
|
||
|
pElem->eTxtAlign, colBg, GSLC_COL_BLACK, nMarginX, nMarginY);
|
||
|
}
|
||
|
|
||
|
// Draw new string content
|
||
|
gslc_DrawTxtBase(pGui, pElem->pStrBuf, pElem->rElem, pElem->pTxtFont, pElem->eTxtFlags,
|
||
|
pElem->eTxtAlign, colTxt, GSLC_COL_BLACK, nMarginX, nMarginY);
|
||
|
|
||
|
// Save a copy of the new string content so we can support future erase
|
||
|
strncpy(pXRingGauge->acStrLast, pElem->pStrBuf, XRING_STR_MAX);
|
||
|
pXRingGauge->acStrLast[XRING_STR_MAX - 1] = 0; // Force null terminator
|
||
|
|
||
|
} // pStrBuf
|
||
|
|
||
|
// Save the position to enable future incremental calculations
|
||
|
pXRingGauge->nValLast = pXRingGauge->nVal;
|
||
|
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
|
||
|
// Mark the element as no longer requiring redraw
|
||
|
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_NONE);
|
||
|
|
||
|
return true;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void gslc_ElemXRingGaugeSetVal(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int16_t nVal)
|
||
|
{
|
||
|
gslc_tsXRingGauge* pXRingGauge = (gslc_tsXRingGauge*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_RING, __LINE__);
|
||
|
if (!pXRingGauge) return;
|
||
|
|
||
|
int16_t nValOld;
|
||
|
|
||
|
// Clip position
|
||
|
if (nVal < pXRingGauge->nValMin) { nVal = pXRingGauge->nValMin; }
|
||
|
if (nVal > pXRingGauge->nValMax) { nVal = pXRingGauge->nValMax; }
|
||
|
|
||
|
// Update
|
||
|
nValOld = pXRingGauge->nVal;
|
||
|
pXRingGauge->nVal = nVal;
|
||
|
|
||
|
// Only update if changed
|
||
|
if (nVal != nValOld) {
|
||
|
// Mark for redraw
|
||
|
// - Only need incremental redraw
|
||
|
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_INC);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void gslc_ElemXRingGaugeSetValRange(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int16_t nValMin, int16_t nValMax)
|
||
|
{
|
||
|
gslc_tsXRingGauge* pXRingGauge = (gslc_tsXRingGauge*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_RING, __LINE__);
|
||
|
if (!pXRingGauge) return;
|
||
|
|
||
|
pXRingGauge->nValMin = nValMin;
|
||
|
pXRingGauge->nValMax = nValMax;
|
||
|
|
||
|
// Mark for full redraw
|
||
|
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
|
||
|
}
|
||
|
|
||
|
void gslc_ElemXRingGaugeSetAngleRange(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int16_t nStart, int16_t nRange, bool bClockwise)
|
||
|
{
|
||
|
gslc_tsXRingGauge* pXRingGauge = (gslc_tsXRingGauge*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_RING, __LINE__);
|
||
|
if (!pXRingGauge) return;
|
||
|
|
||
|
pXRingGauge->nAngStart = nStart;
|
||
|
pXRingGauge->nAngRange = nRange;
|
||
|
|
||
|
nRange = (nRange == 0) ? 1 : nRange; // Guard against div/0
|
||
|
|
||
|
// TODO: Support bClockwise
|
||
|
|
||
|
// Mark for full redraw
|
||
|
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
|
||
|
}
|
||
|
|
||
|
void gslc_ElemXRingGaugeSetThickness(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, int8_t nThickness)
|
||
|
{
|
||
|
gslc_tsXRingGauge* pXRingGauge = (gslc_tsXRingGauge*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_RING, __LINE__);
|
||
|
if (!pXRingGauge) return;
|
||
|
|
||
|
pXRingGauge->nThickness = nThickness;
|
||
|
|
||
|
// Mark for full redraw
|
||
|
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
|
||
|
}
|
||
|
|
||
|
void gslc_ElemXRingGaugeSetColorActiveFlat(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, gslc_tsColor colActive)
|
||
|
{
|
||
|
gslc_tsXRingGauge* pXRingGauge = (gslc_tsXRingGauge*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_RING, __LINE__);
|
||
|
if (!pXRingGauge) return;
|
||
|
|
||
|
pXRingGauge->bGradient = false;
|
||
|
pXRingGauge->colRing1 = colActive;
|
||
|
pXRingGauge->colRing2 = colActive;
|
||
|
|
||
|
// Mark for full redraw
|
||
|
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
|
||
|
}
|
||
|
|
||
|
void gslc_ElemXRingGaugeSetColorActiveGradient(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, gslc_tsColor colStart, gslc_tsColor colEnd)
|
||
|
{
|
||
|
gslc_tsXRingGauge* pXRingGauge = (gslc_tsXRingGauge*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_RING, __LINE__);
|
||
|
if (!pXRingGauge) return;
|
||
|
|
||
|
pXRingGauge->bGradient = true;
|
||
|
pXRingGauge->colRing1 = colStart;
|
||
|
pXRingGauge->colRing2 = colEnd;
|
||
|
|
||
|
// Mark for full redraw
|
||
|
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
|
||
|
}
|
||
|
|
||
|
|
||
|
void gslc_ElemXRingGaugeSetColorInactive(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, gslc_tsColor colInactive)
|
||
|
{
|
||
|
gslc_tsXRingGauge* pXRingGauge = (gslc_tsXRingGauge*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_RING, __LINE__);
|
||
|
if (!pXRingGauge) return;
|
||
|
|
||
|
pXRingGauge->colRingRemain = colInactive;
|
||
|
|
||
|
// Mark for full redraw
|
||
|
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void gslc_ElemXRingGaugeSetQuality(gslc_tsGui* pGui, gslc_tsElemRef* pElemRef, uint16_t nSegments)
|
||
|
{
|
||
|
gslc_tsXRingGauge* pXRingGauge = (gslc_tsXRingGauge*)gslc_GetXDataFromRef(pGui, pElemRef, GSLC_TYPEX_RING, __LINE__);
|
||
|
if (!pXRingGauge) return;
|
||
|
|
||
|
nSegments = (nSegments == 0) ? 72 : nSegments; // Guard against div/0 with default
|
||
|
pXRingGauge->nQuality = nSegments;
|
||
|
|
||
|
// Mark for full redraw
|
||
|
gslc_ElemSetRedraw(pGui,pElemRef,GSLC_REDRAW_FULL);
|
||
|
}
|
||
|
|
||
|
|
||
|
// ============================================================================
|