/* 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 #include #include /** * \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> 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