603 lines
15 KiB
C++
603 lines
15 KiB
C++
/*
|
|
MD_Parola - Library for modular scrolling text and Effects
|
|
|
|
See header file for comments
|
|
|
|
Copyright (C) 2013 Marco Colli. All rights reserved.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
#include <MD_Parola.h>
|
|
#include <MD_Parola_lib.h>
|
|
#include <MD_MAX72xx.h>
|
|
/**
|
|
* \file
|
|
* \brief Implements MD_PZone class methods
|
|
*/
|
|
|
|
MD_PZone::MD_PZone(void) : _fsmState(END), _scrollDistance(0), _zoneEffect(0), _charSpacing(1),
|
|
_fontDef(nullptr), _userChars(nullptr), _cBufSize(0), _cBuf(nullptr)
|
|
#if ENA_SPRITE
|
|
, _spriteInData(nullptr), _spriteOutData(nullptr)
|
|
#endif
|
|
{
|
|
};
|
|
|
|
MD_PZone::~MD_PZone(void)
|
|
{
|
|
// release the memory for user defined characters
|
|
charDef_t *p = _userChars;
|
|
|
|
while (p!= nullptr)
|
|
{
|
|
charDef_t *pt = p;
|
|
p = pt->next;
|
|
delete pt;
|
|
};
|
|
|
|
// release memory for the character buffer
|
|
delete[] _cBuf;
|
|
}
|
|
|
|
void MD_PZone::begin(MD_MAX72XX *p)
|
|
{
|
|
_MX = p;
|
|
allocateFontBuffer();
|
|
}
|
|
|
|
void MD_PZone::allocateFontBuffer(void)
|
|
{
|
|
uint8_t size = _MX->getMaxFontWidth() + getCharSpacing();
|
|
PRINTS("\nallocateFontBuffer");
|
|
if (size > _cBufSize)
|
|
{
|
|
delete[] _cBuf;
|
|
_cBufSize = size;
|
|
PRINT(" new size ", _cBufSize);
|
|
_cBuf = new uint8_t[_cBufSize];
|
|
}
|
|
}
|
|
|
|
void MD_PZone::setZoneEffect(boolean b, zoneEffect_t ze)
|
|
{
|
|
switch (ze)
|
|
{
|
|
case PA_FLIP_LR: _zoneEffect = (b ? ZE_SET(_zoneEffect, ZE_FLIP_LR_MASK) : ZE_RESET(_zoneEffect, ZE_FLIP_LR_MASK)); break;
|
|
case PA_FLIP_UD: _zoneEffect = (b ? ZE_SET(_zoneEffect, ZE_FLIP_UD_MASK) : ZE_RESET(_zoneEffect, ZE_FLIP_UD_MASK)); break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
boolean MD_PZone::getZoneEffect(zoneEffect_t ze)
|
|
{
|
|
switch (ze)
|
|
{
|
|
case PA_FLIP_LR: return(ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK)); break;
|
|
case PA_FLIP_UD: return(ZE_TEST(_zoneEffect, ZE_FLIP_UD_MASK)); break;
|
|
}
|
|
|
|
return(false);
|
|
}
|
|
|
|
#if ENA_SPRITE
|
|
void MD_PZone::setSpriteData(uint8_t *inData, uint8_t inWidth, uint8_t inFrames, uint8_t* outData, uint8_t outWidth, uint8_t outFrames)
|
|
{
|
|
_spriteInData = inData;
|
|
_spriteInWidth = inWidth;
|
|
_spriteInFrames = inFrames;
|
|
_spriteOutData = outData;
|
|
_spriteOutWidth = outWidth;
|
|
_spriteOutFrames = outFrames;
|
|
}
|
|
#endif
|
|
|
|
void MD_PZone::setInitialConditions(void)
|
|
// set the global variables initial conditions for all display effects
|
|
{
|
|
PRINTS("\nsetInitialConditions");
|
|
|
|
if (_pText == nullptr)
|
|
return;
|
|
|
|
_pCurChar = _pText;
|
|
_limitOverflow = !calcTextLimits(_pText);
|
|
}
|
|
|
|
void MD_PZone::setInitialEffectConditions(void)
|
|
// set the initial conditions for loops in the FSM
|
|
{
|
|
PRINTS("\nsetInitialFSMConditions");
|
|
|
|
_startPos = _nextPos = (_textAlignment == PA_RIGHT ? _limitRight : _limitLeft);
|
|
_endPos = (_textAlignment == PA_RIGHT ? _limitLeft : _limitRight);
|
|
_posOffset = (_textAlignment == PA_RIGHT ? 1 : -1);
|
|
}
|
|
|
|
uint16_t MD_PZone::getTextWidth(char *p)
|
|
// Get the width in columns for the text string passed to the function
|
|
// This is the sum of all the characters and the space between them.
|
|
{
|
|
uint16_t sum = 0;
|
|
|
|
PRINT("\ngetTextWidth: ", p);
|
|
|
|
while (*p != '\0')
|
|
{
|
|
sum += findChar(*p++, _cBufSize, _cBuf);
|
|
if (*p) sum += _charSpacing; // next character is not nul, so add inter-character spacing
|
|
}
|
|
|
|
PRINT("\ngetTextWidth: W=", sum);
|
|
|
|
return(sum);
|
|
}
|
|
|
|
bool MD_PZone::calcTextLimits(char *p)
|
|
// Work out left and right sides for the text to be displayed,
|
|
// depending on the text alignment. If the message will not fit
|
|
// in the current display the return false, otherwise true.
|
|
{
|
|
bool b = true;
|
|
uint16_t displayWidth = ZONE_END_COL(_zoneEnd) - ZONE_START_COL(_zoneStart) + 1;
|
|
|
|
_textLen = getTextWidth(p);
|
|
|
|
PRINT("\ncalcTextLimits: disp=", displayWidth);
|
|
PRINT(" text=", _textLen);
|
|
|
|
switch (_textAlignment)
|
|
{
|
|
case PA_LEFT:
|
|
_limitLeft = ZONE_END_COL(_zoneEnd);
|
|
if (_textLen > displayWidth)
|
|
{
|
|
_limitRight = ZONE_START_COL(_zoneStart);
|
|
b = false;
|
|
}
|
|
else
|
|
{
|
|
_limitRight = _limitLeft - _textLen + 1;
|
|
}
|
|
break;
|
|
|
|
case PA_RIGHT:
|
|
_limitRight = ZONE_START_COL(_zoneStart);
|
|
if (_textLen > displayWidth)
|
|
{
|
|
_limitLeft = ZONE_END_COL(_zoneEnd);
|
|
b = false;
|
|
}
|
|
else
|
|
{
|
|
_limitLeft = _limitRight + _textLen - 1;
|
|
}
|
|
break;
|
|
|
|
case PA_CENTER:
|
|
if (_textLen > displayWidth)
|
|
{
|
|
_limitLeft = ZONE_END_COL(_zoneEnd);
|
|
_limitRight = ZONE_START_COL(_zoneStart);
|
|
b= false;
|
|
}
|
|
else
|
|
{
|
|
_limitRight = ZONE_START_COL(_zoneStart) + ((displayWidth - _textLen)/2);
|
|
_limitLeft = _limitRight + _textLen - 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
PRINT(" -> L:", _limitLeft);
|
|
PRINT(" R:", _limitRight);
|
|
PRINT(" Oveflow:", !b);
|
|
|
|
return (b);
|
|
}
|
|
|
|
bool MD_PZone::addChar(uint8_t code, uint8_t *data)
|
|
// Add a user defined character to the replacement list
|
|
{
|
|
charDef_t *pcd;
|
|
|
|
if (code == 0)
|
|
return(false);
|
|
|
|
PRINTX("\naddChar 0x", code);
|
|
|
|
// first see if we have the code in our list
|
|
pcd = _userChars;
|
|
while (pcd != nullptr)
|
|
{
|
|
if (pcd->code == code)
|
|
{
|
|
pcd->data = data;
|
|
PRINTS(" found existing in list");
|
|
return(true);
|
|
}
|
|
pcd = pcd->next;
|
|
}
|
|
|
|
// Now see if we have an empty slot in our list
|
|
pcd = _userChars;
|
|
while (pcd != nullptr)
|
|
{
|
|
if (pcd->code == 0)
|
|
{
|
|
pcd->code = code;
|
|
pcd->data = data;
|
|
PRINTS(" found empty slot");
|
|
return(true);
|
|
}
|
|
pcd = pcd->next;
|
|
}
|
|
|
|
// default is to add a new node to the front of the list
|
|
if ((pcd = new charDef_t) != nullptr)
|
|
{
|
|
pcd->code = code;
|
|
pcd->data = data;
|
|
pcd->next = _userChars;
|
|
_userChars = pcd;
|
|
PRINTS(" added new node");
|
|
}
|
|
else
|
|
{
|
|
PRINTS(" failed allocating new node");
|
|
}
|
|
|
|
return(pcd != nullptr);
|
|
}
|
|
|
|
bool MD_PZone::delChar(uint8_t code)
|
|
// Delete a user defined character from the replacement list
|
|
{
|
|
charDef_t *pcd = _userChars;
|
|
|
|
if (code == 0)
|
|
return(false);
|
|
|
|
// Scan down the linked list
|
|
while (pcd != nullptr)
|
|
{
|
|
if (pcd->code == code)
|
|
{
|
|
pcd->code = 0;
|
|
pcd->data = nullptr;
|
|
break;
|
|
}
|
|
pcd = pcd->next;
|
|
}
|
|
|
|
return(pcd != nullptr);
|
|
}
|
|
|
|
uint8_t MD_PZone::findChar(uint8_t code, uint8_t size, uint8_t *cBuf)
|
|
// Find a character either in user defined list or from font table
|
|
{
|
|
charDef_t *pcd = _userChars;
|
|
uint8_t len;
|
|
|
|
PRINTX("\nfindUserChar 0x", code);
|
|
// check local list first
|
|
while (pcd != nullptr)
|
|
{
|
|
PRINTX(" ", pcd->code);
|
|
if (pcd->code == code) // found it
|
|
{
|
|
PRINTS(" found character");
|
|
len = min(size, pcd->data[0]);
|
|
memcpy(cBuf, &pcd->data[1], len);
|
|
return(len);
|
|
}
|
|
pcd = pcd->next;
|
|
}
|
|
|
|
// get it from the standard font
|
|
PRINTS(" no user char");
|
|
_MX->setFont(_fontDef); // change to the font for this zone
|
|
len = _MX->getChar(code, size, cBuf);
|
|
|
|
return(len);
|
|
}
|
|
|
|
uint8_t MD_PZone::makeChar(char c, bool addBlank)
|
|
// Load a character bitmap and add in trailing char spacing blanks
|
|
{
|
|
uint8_t len;
|
|
|
|
|
|
// look for the character
|
|
len = findChar((uint8_t)c, _cBufSize, _cBuf);
|
|
|
|
PRINTX("\nmakeChar 0x", c);
|
|
PRINT(", len=", len);
|
|
|
|
// Add in the inter char spacing
|
|
if (addBlank)
|
|
{
|
|
for (uint8_t i = 0; i < _charSpacing; i++)
|
|
{
|
|
if (len < _cBufSize)
|
|
_cBuf[len++] = 0;
|
|
}
|
|
}
|
|
|
|
return(len);
|
|
}
|
|
|
|
void MD_PZone::reverseBuf(uint8_t *p, uint8_t size)
|
|
// reverse the elements of the specified buffer
|
|
// useful when we are scrolling right and want to insert the columns in reverse order
|
|
{
|
|
for (uint8_t i=0; i<size/2; i++)
|
|
{
|
|
uint8_t t;
|
|
|
|
t = p[i];
|
|
p[i] = p[size-1-i];
|
|
p[size-1-i] = t;
|
|
}
|
|
}
|
|
|
|
void MD_PZone::invertBuf(uint8_t *p, uint8_t size)
|
|
// invert the elements of the specified buffer
|
|
// used when the character needs to be inverted when ZE_FLIP_UD
|
|
{
|
|
for (uint8_t i=0; i<size; i++)
|
|
{
|
|
uint8_t v = p[i];
|
|
|
|
v = ((v >> 1) & 0x55) | ((v & 0x55) << 1); // swap odd and even bits
|
|
v = ((v >> 2) & 0x33) | ((v & 0x33) << 2); // swap consecutive pairs
|
|
v = ((v >> 4) & 0x0F) | ((v & 0x0F) << 4); // swap nibbles ...
|
|
|
|
p[i] = v;
|
|
}
|
|
}
|
|
|
|
void MD_PZone::moveTextPointer(void)
|
|
// This method works when increment is done AFTER processing the character
|
|
// The _endOfText flag is set as a look ahead (ie, when the last character
|
|
// is still valid)
|
|
// We need to move a pointer forward or back, depending on the way we are
|
|
// travelling through the text buffer.
|
|
{
|
|
PRINTS("\nMovePtr");
|
|
|
|
if ((!ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK) && SFX(PA_SCROLL_RIGHT)) ||
|
|
(ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK) && !SFX(PA_SCROLL_RIGHT)))
|
|
{
|
|
PRINTS(" --");
|
|
_endOfText = (_pCurChar == _pText);
|
|
_pCurChar--;
|
|
}
|
|
else
|
|
{
|
|
PRINTS(" ++");
|
|
_pCurChar++;
|
|
_endOfText = (*_pCurChar == '\0');
|
|
}
|
|
|
|
PRINT(": endOfText ", _endOfText);
|
|
}
|
|
|
|
uint8_t MD_PZone::getFirstChar(void)
|
|
// load the first char into the char buffer
|
|
// return 0 if there are no characters
|
|
{
|
|
uint8_t len = 0;
|
|
|
|
PRINT("\ngetFirst SFX(RIGHT):", SFX(PA_SCROLL_RIGHT));
|
|
PRINT(" ZETEST(UD):", ZE_TEST(_zoneEffect, ZE_FLIP_UD_MASK));
|
|
PRINT(" ZETEST(LR):", ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK));
|
|
|
|
// initialise pointers and make sure we have a good string to process
|
|
_pCurChar = _pText;
|
|
if ((_pCurChar == nullptr) || (*_pCurChar == '\0'))
|
|
{
|
|
_endOfText = true;
|
|
return(0);
|
|
}
|
|
_endOfText = false;
|
|
if ((!ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK) && (SFX(PA_SCROLL_RIGHT))) ||
|
|
(ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK) && !SFX(PA_SCROLL_RIGHT)))
|
|
{
|
|
PRINTS("\nReversed String");
|
|
_pCurChar += strlen(_pText) - 1;
|
|
}
|
|
|
|
// good string, get the first char into the current buffer
|
|
len = makeChar(*_pCurChar, *(_pCurChar+1) != '\0');
|
|
|
|
if ((!ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK) && (SFX(PA_SCROLL_RIGHT))) ||
|
|
(ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK) && !SFX(PA_SCROLL_RIGHT)))
|
|
{
|
|
PRINTS("\nReverse Buffer");
|
|
reverseBuf(_cBuf, len);
|
|
}
|
|
|
|
if ZE_TEST(_zoneEffect, ZE_FLIP_UD_MASK)
|
|
{
|
|
PRINTS("\nInvert buffer");
|
|
invertBuf(_cBuf, len);
|
|
}
|
|
|
|
moveTextPointer();
|
|
|
|
return(len);
|
|
}
|
|
|
|
uint8_t MD_PZone::getNextChar(void)
|
|
// load the next char into the char buffer
|
|
// return 0 if there are no characters
|
|
{
|
|
uint8_t len = 0;
|
|
|
|
PRINT("\ngetNexChar SFX(RIGHT):", SFX(PA_SCROLL_RIGHT));
|
|
PRINT(" ZETEST(UD):", ZE_TEST(_zoneEffect, ZE_FLIP_UD_MASK));
|
|
PRINT(" ZETEST(LR):", ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK));
|
|
|
|
if (_endOfText)
|
|
return(0);
|
|
|
|
len = makeChar(*_pCurChar, *(_pCurChar + 1) != '\0');
|
|
|
|
if ((!ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK) && (SFX(PA_SCROLL_RIGHT))) ||
|
|
(ZE_TEST(_zoneEffect, ZE_FLIP_LR_MASK) && !SFX(PA_SCROLL_RIGHT)))
|
|
{
|
|
PRINTS("\nReversed Buffer");
|
|
reverseBuf(_cBuf, len);
|
|
}
|
|
|
|
if ZE_TEST(_zoneEffect, ZE_FLIP_UD_MASK)
|
|
{
|
|
PRINTS("\nInvert Buffer");
|
|
invertBuf(_cBuf, len);
|
|
}
|
|
|
|
moveTextPointer();
|
|
|
|
return(len);
|
|
}
|
|
|
|
bool MD_PZone::zoneAnimate(void)
|
|
{
|
|
#if TIME_PROFILING
|
|
static uint32_t cycleStartTime;
|
|
#endif
|
|
|
|
_animationAdvanced = false; // assume this will not happen this time around
|
|
|
|
if (_fsmState == END)
|
|
return(true);
|
|
|
|
// work through things that stop us running this at all
|
|
if (((_fsmState == PAUSE) && (millis() - _lastRunTime < _pauseTime)) ||
|
|
(millis() - _lastRunTime < _tickTime) ||
|
|
(_suspend))
|
|
return(false);
|
|
|
|
// save the time now, before we run the animation, so that the animation is part of the
|
|
// delay between animations giving more accurate frame timing.
|
|
_lastRunTime = millis();
|
|
_animationAdvanced = true; // we now know it will happen!
|
|
|
|
// any text to display?
|
|
if (_pText != nullptr)
|
|
{
|
|
switch (_fsmState)
|
|
{
|
|
case END: // do nothing in this state
|
|
PRINT_STATE("ANIMATE");
|
|
break;
|
|
|
|
case INITIALISE:
|
|
PRINT_STATE("ANIMATE");
|
|
#if TIME_PROFILING
|
|
cycleStartTime = millis();
|
|
#endif
|
|
setInitialConditions();
|
|
_moveIn = true;
|
|
// fall through to process the effect, first call will be with INITIALISE
|
|
|
|
default: // All state except END are handled by the special effect functions
|
|
PRINT_STATE("ANIMATE");
|
|
switch (_moveIn ? _effectIn : _effectOut)
|
|
{
|
|
case PA_PRINT: effectPrint(_moveIn); break;
|
|
case PA_SCROLL_UP: effectVScroll(true, _moveIn); break;
|
|
case PA_SCROLL_DOWN: effectVScroll(false, _moveIn); break;
|
|
case PA_SCROLL_LEFT: effectHScroll(true, _moveIn); break;
|
|
case PA_SCROLL_RIGHT: effectHScroll(false, _moveIn); break;
|
|
#if ENA_MISC
|
|
case PA_SLICE: effectSlice(_moveIn); break;
|
|
case PA_MESH: effectMesh(_moveIn); break;
|
|
case PA_FADE: effectFade(_moveIn); break;
|
|
case PA_BLINDS: effectBlinds(_moveIn); break;
|
|
case PA_DISSOLVE: effectDissolve(_moveIn); break;
|
|
case PA_RANDOM: effectRandom(_moveIn); break;
|
|
#endif // ENA_MISC
|
|
#if ENA_SPRITE
|
|
case PA_SPRITE:
|
|
effectSprite(_moveIn, _moveIn ? _effectIn : _effectOut); break;
|
|
#endif // ENA_SPRITE
|
|
#if ENA_WIPE
|
|
case PA_WIPE: effectWipe(false, _moveIn); break;
|
|
case PA_WIPE_CURSOR: effectWipe(true, _moveIn); break;
|
|
#endif // ENA_WIPE
|
|
#if ENA_SCAN
|
|
case PA_SCAN_HORIZX: effectHScan(_moveIn, true); break;
|
|
case PA_SCAN_HORIZ: effectHScan(_moveIn, false); break;
|
|
case PA_SCAN_VERTX: effectVScan(_moveIn, true); break;
|
|
case PA_SCAN_VERT: effectVScan(_moveIn, false); break;
|
|
#endif // ENA_SCAN
|
|
#if ENA_OPNCLS
|
|
case PA_OPENING: effectOpen(false, _moveIn); break;
|
|
case PA_OPENING_CURSOR: effectOpen(true, _moveIn); break;
|
|
case PA_CLOSING: effectClose(false, _moveIn);break;
|
|
case PA_CLOSING_CURSOR: effectClose(true, _moveIn); break;
|
|
#endif // ENA_OPNCLS
|
|
#if ENA_SCR_DIA
|
|
case PA_SCROLL_UP_LEFT: effectDiag(true, true, _moveIn); break;
|
|
case PA_SCROLL_UP_RIGHT: effectDiag(true, false, _moveIn); break;
|
|
case PA_SCROLL_DOWN_LEFT: effectDiag(false, true, _moveIn); break;
|
|
case PA_SCROLL_DOWN_RIGHT: effectDiag(false, false, _moveIn);break;
|
|
#endif // ENA_SCR_DIA
|
|
#if ENA_GROW
|
|
case PA_GROW_UP: effectGrow(true, _moveIn); break;
|
|
case PA_GROW_DOWN: effectGrow(false, _moveIn); break;
|
|
#endif // ENA_GROW
|
|
default:
|
|
_fsmState = END;
|
|
}
|
|
|
|
// one way toggle for input to output, reset on initialize
|
|
_moveIn = _moveIn && !(_fsmState == PAUSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if TIME_PROFILING
|
|
Serial.print("\nAnim time: ");
|
|
Serial.print(millis()-_lastRunTime);
|
|
if (_fsmState == END)
|
|
{
|
|
Serial.print("\nCycle time: ");
|
|
Serial.print(millis()-cycleStartTime);
|
|
}
|
|
#endif
|
|
|
|
return(_fsmState == END);
|
|
}
|
|
|
|
#if DEBUG_PAROLA_FSM
|
|
char *MD_PZone::state2string(fsmState_t s)
|
|
{
|
|
switch (s)
|
|
{
|
|
case INITIALISE: return("INIT");
|
|
case GET_FIRST_CHAR: return("FIRST");
|
|
case GET_NEXT_CHAR: return("NEXT");
|
|
case PUT_CHAR: return("PUT");
|
|
case PUT_FILLER: return("FILLER");
|
|
case PAUSE: return("PAUSE");
|
|
case END: return("END");
|
|
}
|
|
return("???");
|
|
};
|
|
#endif
|