void MD_PZone::effectDissolve(bool bIn)
// Dissolve the current message in/out
{
	switch (_fsmState)
	{
	case INITIALISE:	// bIn = true
	case PAUSE:			// bIn = false
	case GET_FIRST_CHAR:	// first stage dissolve
		PRINT_STATE("IO DISS");
		for (uint16_t i=ZONE_START_COL(_zoneStart); i<=ZONE_END_COL(_zoneEnd); i++)
		{
			uint8_t	col = DATA_BAR(_MX->getColumn(i));
				
			col |= (i&1 ? 0x55 : 0xaa);	// checkerboard pattern
			_MX->setColumn(i, DATA_BAR(col));
		}
		_fsmState = GET_NEXT_CHAR;
		break;

	case GET_NEXT_CHAR:		// second stage dissolve
		PRINT_STATE("IO DISS");
		zoneClear();
		if (bIn) commonPrint();
		for (uint16_t i=ZONE_START_COL(_zoneStart); i<=ZONE_END_COL(_zoneEnd); i++)
		{
			uint8_t	col = DATA_BAR(_MX->getColumn(i));
				
			col |= (i&1 ? 0xaa : 0x55);	// alternate checkerboard pattern
			_MX->setColumn(i, DATA_BAR(col));
		}
		_fsmState = PUT_CHAR;
		break;

	case PUT_CHAR:
		PRINT_STATE("IO DISS");
		zoneClear();
		if (bIn) commonPrint();
		_fsmState = (bIn ? PAUSE : END);
		break;

	default:
		PRINT_STATE("IO DISS");
		_fsmState = (bIn ? PAUSE : END);
	}
}
void MD_PZone::effectPrint(bool bIn)
// Just print the message in the justification selected
{
	if (bIn)	// incoming
	{
		commonPrint();
		_fsmState = PAUSE;
	}
	else	//exiting
	{
		zoneClear();
		_fsmState = END;
	}
}
void MD_PZone::effectVScroll(bool bUp, bool bIn)
// Scroll the display horizontally up of down, depending on the selected effect
{
	if (bIn)	// incoming
	{
		switch (_fsmState)
		{
		case INITIALISE:
			PRINT_STATE("I VSCROLL");
			_nextPos = 0;
			_MX->control(_zoneStart, _zoneEnd, MD_MAX72XX::WRAPAROUND, MD_MAX72XX::OFF);
			_fsmState = PUT_CHAR;
			// fall through to next state

		case GET_FIRST_CHAR:
		case GET_NEXT_CHAR:
		case PUT_CHAR:
		case PAUSE:
			PRINT_STATE("I VSCROLL");

			zoneClear();
			commonPrint();

			for (uint8_t i = _nextPos; i < 7; i++)
				// scroll the whole display so that the message appears to be animated
				// Note: Directions are reversed because we start with the message in the 
				// middle position thru commonPrint() and to see it animated move DOWN we 
				// need to scroll it UP, and vice versa.
				_MX->transform(_zoneStart, _zoneEnd, bUp ? MD_MAX72XX::TSD : MD_MAX72XX::TSU);

			// check if we have finished
			if (_nextPos == 7) _fsmState = PAUSE;

			_nextPos++;
			break;

		default:
			PRINT_STATE("I VSCROLL");
			_fsmState = PAUSE;
		}
	}
	else	// exiting
	{
		switch (_fsmState)
		{
		case PAUSE:
		case INITIALISE:
			PRINT_STATE("O VSCROLL");
			_nextPos = 0;
			_fsmState = PUT_CHAR;
			// fall through to next state

		case GET_FIRST_CHAR:
		case GET_NEXT_CHAR:
		case PUT_CHAR:
			PRINT_STATE("O VSCROLL");

			_MX->transform(_zoneStart, _zoneEnd, bUp ? MD_MAX72XX::TSU : MD_MAX72XX::TSD);

			// check if we have finished
			if (_nextPos == 7) _fsmState = END;

			_nextPos++;
			break;

		default:
			PRINT_STATE("O VSCROLL");
			_fsmState = END;
			break;
		}
	}
}
void MD_PZone::effectClose(bool bLightBar, bool bIn)
// Dissolve the current message in/out
{
  if (bIn)
  {
    switch (_fsmState)
    {
    case INITIALISE:
    case GET_FIRST_CHAR:
    case GET_NEXT_CHAR:
      PRINT_STATE("I CLOSE");
      _nextPos = 0;
      zoneClear();
      if (bLightBar)
      {
        _MX->setColumn(_limitLeft, LIGHT_BAR);
        _MX->setColumn(_limitRight,LIGHT_BAR);
      }
      _fsmState = PUT_CHAR;
      // fall through

    case PUT_CHAR:
      PRINT_STATE("I CLOSE");
      FSMPRINT(" - offset ", _nextPos);
      zoneClear();
      commonPrint();
      {
        const int16_t	halfWidth = (_limitLeft - _limitRight)/2;

        if (_nextPos > halfWidth)
        {
          _fsmState = PAUSE;
        }
        else
        {
          for (int16_t i = _limitRight + _nextPos + 1; i < _limitLeft - _nextPos; i++)
            _MX->setColumn(i, EMPTY_BAR);

          _nextPos++;
          if (bLightBar && (_nextPos <= halfWidth))
          {
            _MX->setColumn(_limitLeft - _nextPos, LIGHT_BAR);
            _MX->setColumn(_limitRight + _nextPos, LIGHT_BAR);
          }
        }
      }
      break;

    default:
      PRINT_STATE("I CLOSE");
      _fsmState = PAUSE;
    }
  }
  else  // exiting
  {
    switch (_fsmState)
    {
    case PAUSE:

    case GET_FIRST_CHAR:
    case GET_NEXT_CHAR:
      PRINT_STATE("O CLOSE");
      FSMPRINT(" - limits R:", _limitRight);
      FSMPRINT(" L:", _limitLeft);
      _nextPos = (_limitLeft-_limitRight)/2;
      FSMPRINT(" O:", _nextPos);
      zoneClear();
      commonPrint();
      if (bLightBar)
      {
        _MX->setColumn(_limitLeft - _nextPos, LIGHT_BAR);
        _MX->setColumn(_limitRight + _nextPos, LIGHT_BAR);
      }
      _fsmState = PUT_CHAR;
      break;

    case PUT_CHAR:
      PRINT_STATE("O CLOSE");
      FSMPRINT(" - offset ", _nextPos);
      if (_nextPos < 0)
      {
        _fsmState = END;
      }
      else
      {
        _MX->setColumn(_limitLeft - _nextPos, EMPTY_BAR);
        _MX->setColumn(_limitRight + _nextPos, EMPTY_BAR);

        _nextPos--;
        if (bLightBar && (_nextPos >= 0))
        {
          _MX->setColumn(_limitLeft - _nextPos, LIGHT_BAR);
          _MX->setColumn(_limitRight + _nextPos, LIGHT_BAR);
        }
      }
      break;

    default:
      PRINT_STATE("O CLOSE");
      _fsmState = END;
    }
  }
}
void MD_PZone::effectSlice(bool bIn)
{
    if (bIn)
    {
        switch(_fsmState)
        {
        case INITIALISE:
        case GET_FIRST_CHAR:
            PRINT_STATE("I SLICE");

            if ((_charCols = getFirstChar()) == 0)
            {
                _fsmState = END;
                break;
            }
            zoneClear();
            _countCols = 0;
            _nextPos = ZONE_START_COL(_zoneStart);
            _endPos = _limitLeft;

            _fsmState = PUT_CHAR;
            break;

        case GET_NEXT_CHAR:	// Load the next character from the font table
            PRINT_STATE("I SLICE");
            // have we reached the end of the characters string?
            if ((_charCols = getNextChar()) == 0)
            {
                _fsmState = PAUSE;
                break;
            }
            _countCols = 0;
            _fsmState = PUT_CHAR;
        // !! fall through to next state to start displaying

        case PUT_CHAR:	// display the next part of the character
            PRINT_STATE("I SLICE");
            FSMPRINT(" - Next ", _endPos);
            FSMPRINT(", anim ", _nextPos);

            if (_cBuf[_countCols] == 0)
            {
                _nextPos = _endPos;	// pretend we just animated it!
            }
            else
            {
                // clear the column and animate the next one
                if (_nextPos != _endPos) _MX->setColumn(_nextPos, EMPTY_BAR);
                _nextPos++;
                _MX->setColumn(_nextPos, DATA_BAR(_cBuf[_countCols]));
            }

            // set up for the next time
            if (_nextPos == _endPos)
            {
                _nextPos = ZONE_START_COL(_zoneStart);
                _countCols++;
                _endPos--;
            }
            if (_countCols == _charCols) _fsmState = GET_NEXT_CHAR;
            break;

        default:
            _fsmState = PAUSE;
        }
    }
    else	// exiting
    {
        switch(_fsmState)
        {
        case PAUSE:
            PRINT_STATE("O SLICE");
            _nextPos = _endPos = _limitLeft;
            _fsmState = PUT_CHAR;
        // fall through

        case GET_FIRST_CHAR:
        case GET_NEXT_CHAR:
        case PUT_CHAR:
            PRINT_STATE("O SLICE");
            FSMPRINT(" - Next ", _endPos);
            FSMPRINT(", anim ", _nextPos);

            while(_MX->getColumn(_nextPos) == EMPTY_BAR && _endPos >= _limitRight)
                _nextPos = _endPos--;	// pretend we just animated it!

            if (_endPos <= _limitRight)
                _fsmState = END;	//reached the end
            else
            {
                // Move the column over to the left and blank out previous position
                if (_nextPos < ZONE_END_COL(_zoneEnd))
                    _MX->setColumn(_nextPos+1, _MX->getColumn(_nextPos));
                _MX->setColumn(_nextPos, EMPTY_BAR);
                _nextPos++;

                // set up for the next time
                if (_nextPos == ZONE_END_COL(_zoneEnd)+1)
                    _nextPos = _endPos--;
            }
            break;

        default:
            _fsmState = END;
        }
    }
}
bool MD_PZone::zoneAnimate(void)
{
	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();

	// any text to display?
	if (_pText != NULL)
	{
		switch (_fsmState)
		{
			case END:		// do nothing in this state
				PRINT_STATE("ANIMATE");
				break;

			case INITIALISE:
				PRINT_STATE("ANIMATE");

				setInitialConditions();
				zoneClear();
				_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
			switch (_moveIn ? _effectIn : _effectOut)
			{
				case PRINT:				effectPrint(_moveIn);			break;
				case SLICE:				effectSlice(_moveIn);			break;
				case WIPE:				effectWipe(false, _moveIn);		break;
				case WIPE_CURSOR:		effectWipe(true, _moveIn);		break;
				case OPENING:			effectOpen(false, _moveIn);		break;
				case OPENING_CURSOR:	effectOpen(true, _moveIn);		break;
				case CLOSING:			effectClose(false, _moveIn);	break;
				case CLOSING_CURSOR:	effectClose(true, _moveIn);		break;
				case BLINDS:			effectBlinds(_moveIn);			break;
				case DISSOLVE:			effectDissolve(_moveIn);		break;
				case SCAN_HORIZ:		effectHScan(_moveIn);			break;
				case SCAN_VERT:			effectVScan(_moveIn);			break;
				case GROW_UP:			effectGrow(true, _moveIn);		break;
				case GROW_DOWN:			effectGrow(false, _moveIn);		break;
				case SCROLL_UP:			effectVScroll(true, _moveIn);	break;
				case SCROLL_DOWN:		effectVScroll(false, _moveIn);	break;
				case SCROLL_LEFT:		effectHScroll(true, _moveIn);	break;
				case SCROLL_RIGHT:		effectHScroll(false, _moveIn);	break;
				case SCROLL_UP_LEFT:	effectDiag(true, true, _moveIn);	break;
				case SCROLL_UP_RIGHT:	effectDiag(true, false, _moveIn);	break;
				case SCROLL_DOWN_LEFT:	effectDiag(false, true, _moveIn);	break;
				case SCROLL_DOWN_RIGHT:	effectDiag(false, false, _moveIn);	break;
				default:
				_fsmState = END;
			}

			// one way toggle for input to output, reset on initialize
			_moveIn = _moveIn && !(_fsmState == PAUSE);
			break;
		}
	}

	TIME_PROFILE("\nAnimation time ");
	TIME_PROFILE(": Cycle time ");

	return(_fsmState == END);
}