serial_debugger/hardware/_controller/src/guislice/XRingGauge.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);
}
// ============================================================================