#include <arduino.h>
#include <Adafruit_GFX.h>
#include <MCUFRIEND_kbv.h>                                              //If you use other display, replace this include.
#include <TouchScreen.h>
#include <Fonts/FreeSans9pt7b.h>
#include <TouchScreen.h>
#include <avr/io.h>
#include "GUIdriver.h"                                                  //Common GUI functions.
#include "GUIuser.h"                                                    //Required for \ref screen_ptr_size_get; \ref SCR_NUMBER

/*****************************************************************************************************************************
 * \ingroup DEFINE
 */
typedef void (*func_ptr_i8_t)(int8_t);

/*****************************************************************************************************************************
 * \ingroup ALLOC
 */
MCUFRIEND_kbv   tft;
TouchScreen     ts = TouchScreen (XP, YP, XM, YM, 300);
uint16_t        screenBgndColor;                                        //Actual Background.
int16_t         xTouch;                                                 //Fast access to touch values without going via stack.
int16_t         yTouch;
SCR_NUMBER      screenNow = SCR_NONE;
int8_t          touchIndex;                                             //Fast access to \ref SCR_DATA array index pressed in the actual Screen.




/*****************************************************************************************************************************
 * \ingroup API.
 * \retval              Return the background color of the last drawn screen.
 */
uint16_t
background_color_get ()
{
  return screenBgndColor;
}






/*****************************************************************************************************************************
 * \ingroup API.
 * Screen View:
 *              tX0, tY0              xt          oHiPct        - The object size is determined by oW and oH.
 *                    ----------------|------------             - The line is vertical located at tY0 + (oH/2).
 *                oLoPct                       tX1, tY1         - The range is tX0 (= oLoPct) to (tX1 - oW) (= oHiPct).
 *
 * - This function can be removed if the BUTTON_SLH object isn't used.
 * - If the BUTTON_SLH object is not correctly configured, it may not be drawn on the screen but this function assumes those bugs are fixed before releasing your app.
 * - Since the compiler sees SCR_DATA pointer is a struct, AVR architecture expects it in RAM. This must be typecasted to
 *   a uint8_t pointer so it can be used for FLASH reads.
 * \param oPtr                Must point to a BUTTON_SLH object.
 * \param pct                 Percentage Fill for the sliderbar (required to determine left or right slider if LoPct is negative and HiPct is positive).
 * \param enXtouch          - TOUCHPOS_REF Use actual xTouch position to determine where to relocate the button object (fastest, only possible by touch operation).
 *                          - TOUCHPOS_CALC (default): compute a virtual touch position (slower, can be used to put the object at a specific position).
 *
 */
void
button_sl_h_pct (const SCR_DATA*       oPtr,                            //Must be a BUTTON_SLH object.
                 const int8_t          pct,                             //Must always be provided as the % click on the bar (max range -128 to + 127)
                 const TOUCH_REFERENCE enXtouch)                        //If this is non-zero, xTouch is used as the reference instead of pct computed.
{
  if (oPtr == NULL) return;
  uint8_t* p = (uint8_t*) (oPtr);
  if (FRwu (p + OF_OBJ) != BUTTON_SLH) return;
  int16_t xt = xTouch;
  if (enXtouch == TOUCHPOS_CALC)
  {                                                                     //This is more source optimized, but AVR is certainly slower using 32 bit values.
    int16_t pixRange = FRws (p + OF_TX1) - FRws (p + OF_TX0);           //Slider Pixel range.
    int16_t pctPos = (int16_t)(pct) - (int16_t)(FRbs (p + OF_LOP));     //Diff pct to lowest possible %.
    int32_t p32 = pixRange * pctPos;
    pctPos = (int16_t)(FRbs (p + OF_HIP) - FRbs (p + OF_LOP));          //%Range.%Range.
    p32 /= pctPos;
    xt = FRws (p + OF_TX0) + (int16_t)(p32);
    if (xt < FRws (p + OF_TX0)) xt = FRws (p + OF_TX0);
  }
  uint16_t ohlf = FRwu (p + OF_OH) >> 1;                                //Object Height Half.
  uint16_t wta = FRwu (p + OF_TX1) - FRwu (p + OF_TX0);                 //Width Touch Area.
  tft.fillRect (FRwu (p + OF_TX0), FRwu (p + OF_TY0), wta, ohlf, screenBgndColor);////Erase above the line.
  tft.fillRect (FRwu (p + OF_TX0), FRwu (p + OF_TY0) + ohlf + 1, wta, ohlf - 1, screenBgndColor);//Erase below the line.
  if (xt > FRws (p + OF_TX1) - FRws (p + OF_OW)) xt = FRws (p + OF_TX1) - FRws (p + OF_OW);//The object x-positon cannot exceed (tX1 - width)
  tft.fillRect (xt, FRwu (p + OF_TY0), FRwu (p + OF_OW), FRwu (p + OF_OH), FRwu (p + OF_OC));//Draw a new position object.
}






/*****************************************************************************************************************************
 * \ingroup API
 * Screen View:
 *              tX0, tY0    |         oHiPct        - The object size is determined by oW and oH.
 *                          |                       - The horizontal line is located at tY0 - (oW/2).
 *                          _ yt                    - Range is tY0 (= oHiPct) to (tY1 - oH) (= oLoPct).
 *                          |
 *                          |
 *                oLoPct    |      tX1, tY1
 *
 * - This function can be removed if the BUTTON_SLV object isn't used.
 * - If the BUTTON_SLV object is not correctly configured, it may not be drawn on the screen but this function assumes those bugs are fixed before releasing your app.
 * \param oPtr                Must point to a BUTTON_SLV object.
 * \param pct                 Percentage Fill for the sliderbar (required to determine left or right slider if LoPct is negative and HiPct is positive).
 * \param enYtouch          - TOUCHPOS_REF Use actual xTouch position to determine where to relocate the button object (fastest, only possible by touch operation).
 *                          - TOUCHPOS_CALC (default): compute a virtual touch position (slower, can be used to put the object at a specific position).
 */
void
button_sl_v_pct (const SCR_DATA*       oPtr,                            //Must be a BUTTON_SLV object.
                 const int8_t          pct,                             //Must always be provided as the % click on the bar (max range -128 to + 127)
                 const TOUCH_REFERENCE enYtouch)                        //If this is non-zero, xTouch is used as the reference instead of pct computed.

{
  if (oPtr == NULL) return;
  uint8_t* p = (uint8_t*) (oPtr);
  if (FRwu (p + OF_OBJ) != BUTTON_SLV) return;
  int16_t yt = yTouch;
  if (enYtouch == TOUCHPOS_CALC)
  {
    int16_t pixRange = FRws (p + OF_TY1) - FRws (p + OF_TY0);           //Slider Pixel range.
    int16_t pctPos = (int16_t)(pct) - (int16_t)(FRbs (p + OF_LOP));     //Diff pct to lowest possible %.
    int32_t pct = pixRange * pctPos;
    pctPos = (int16_t)(FRbs (p + OF_HIP) - FRbs (p + OF_LOP));          //%Range.
    pct /= pctPos;
    yt = FRws (p + OF_TY1) - (int16_t)(pct);
    if (yt < FRws (p + OF_TY0)) yt = FRws (p + OF_TY0);
  }
  uint16_t ohlf = FRwu (p + OF_OW) >> 1;                                //Object Width Half.
  uint16_t hta = FRwu (p + OF_TY1) - FRwu (p + OF_TY0);                 //Height Touch Area.
  tft.fillRect (FRwu (p + OF_TX0), FRwu (p + OF_TY0), ohlf, hta, screenBgndColor);//Erase left from the line.
  tft.fillRect (FRwu (p + OF_TX0) + ohlf + 1, FRwu (p + OF_TY0), ohlf - 1, hta, screenBgndColor);//Erase right from the line.
  if (yt > (FRws (p + OF_TY1) - FRws (p + OF_OH))) yt = FRws (p + OF_TY1) - FRws (p + OF_OH);
  tft.fillRect (FRwu (p + OF_TX0), yt, FRwu (p + OF_OW), FRwu (p + OF_OH), FRwu (p + OF_OC));
}






/*****************************************************************************************************************************
 * \ingroup API.
 * Toggle a CHECKBOX object.
 */
void
checkbox_update (const SCR_DATA* sd,
                 const uint8_t   state)
{
  if (sd == NULL) return;
  uint8_t* p = (uint8_t*) (sd);
  if (FRwu (p + OF_OBJ) != CHECKBOX) return;
  uint16_t oW =  FRwu (p + OF_OW);
  uint16_t oH =  FRwu (p + OF_OH);
  if (state == 0) tft.fillRect (FRwu (p + OF_TX0) + CHECKBOX_THICK, FRwu (p + OF_TY0) + CHECKBOX_THICK, oW - (CHECKBOX_THICK << 1), oH - (CHECKBOX_THICK << 1), screenBgndColor);
  else switch (FRwu (p + OF_OE))
  {
  default:  tft.fillRect (FRwu (p + OF_OX0), FRwu (p + OF_OY0), oW, oH, FRwu (p + OF_OC));//Complete rectangle.
            break;
  case 1:   {                                                           //X-fill.
              uint16_t epx = FRwu (p + OF_OX0) + oW;
              uint16_t epy = FRwu (p + OF_OY0) + FRwu (p + OF_OH);
              tft.drawLine (FRwu (p + OF_OX0), FRwu (p + OF_OY0), epx, epy, FRwu (p + OF_OC));
              tft.drawLine (FRwu (p + OF_OX0), epy, epx, FRwu (p + OF_OY0), FRwu (p + OF_OC));
            }
            break;
  }
}







/*****************************************************************************************************************************
 * \ingroup API
 * Draw a screen. The first screen after power on must be called in the \ref setup_screen_ext Arduino extension function.
 * From such 'base'screen, all others can  be 'launched'.
 * Note the input buffer (used by the numerical input function) is always cleared even if not used.
 * \param screenWanted        SCR_NUMBER to select the screen.
 * \param drawMode            Normal draw (only write if screen has changed or forced with/without clearing it first).
 * \retval                    Error number (0 = no error; positive = warning; negative = error).
 */
int8_t
screen_draw (SCR_NUMBER screenWanted,
             SCR_UPDATE drawMode)
{
//DEBUGPRINTLL ((__func__)); DEBUGPRINT ((" #")); DEBUGPRINTN ((screenWanted));
  if (   (screenWanted == screenNow)
      && (drawMode == SCR_DRAW)) return (0);
  input_buffer_clear ();
  if (screenWanted == SCR_NONE)
  {
    screenNow = SCR_NONE;
    return (0);
  }
  const SCR_DATA* sd;
  uint8_t  objCount;
  screen_ptr_size_get (screenWanted, &sd, &objCount);
  if (   (sd != NULL)
      && (objCount))
  {
    screenBgndColor = BLACK;
    if (FRwu ((uint8_t*) (sd) + OF_OBJ) == BACKGROUND) screenBgndColor = FRwu ((uint8_t*) (sd) + OF_OC);
    if (   (screenWanted != screenNow)
        || (drawMode == SCR_REDRAW_W_CLR)) tft.fillScreen (screenBgndColor);
    screen_obj_draw (sd, objCount);
    screenNow = screenWanted;
    return (0);
  }
  return (-128);
}






/*****************************************************************************************************************************
 * \ingroup API
 * Return the actual Screen Number displayed. Within this source code, the screen number is read directly from the global variable.
 * This call is mainly provided so that external functions can interrogate the actual screen number.
 * \param                     None
 * \retval                    \ref SCR_NUMBER.
 */
SCR_NUMBER
screen_no_get ()
{
  return (screenNow);
}





/*****************************************************************************************************************************
 * \ingroup API
 * Draw a single object from the actual selected screen data to the TFT. See the header file for details.
 * Note the actual input (numerical input) is subject to be modified or removed in your application.
 * \param sd                  The 1st memory address for an \ref SCR_DATA array that must be drawn (Screen Data).
 * \param count               Amount of Screen Data Objects.
 * \retval                    None
*/
void
screen_obj_draw (const SCR_DATA* sd,                                    //References to RAM, but we are using from Flash.
                 uint8_t         count)
{
//DEBUGPRINTLL ((__func__)); DEBUGPRINT ((" cnt=")); DEBUGPRINTN ((count));
  if (   (sd == NULL)
      || (count == 0)) return;
  input_object_set (NULL);
  for (uint8_t obj = 0; obj < count; ++obj)
  {
    uint8_t* p = (uint8_t*) (&sd[obj]);                                 //Dereference the RAM pointer to a byte based FLASH pointer.
    uint16_t oC = FRwu (p + OF_OC);                                     //Read the struct members based on offset.
    uint16_t oX0 = FRwu (p + OF_OX0);
    uint16_t oW = FRwu (p + OF_OW);
    uint16_t oY0 = FRwu (p + OF_OY0);
    uint16_t oH = FRwu (p + OF_OH);
    uint16_t lx0 = FRwu (p + OF_LX0);                                   //Label X Position.
    uint16_t ly0 = FRwu (p + OF_LY0);                                   //Label Y Position.
    uint16_t oE = FRwu (p + OF_OE);
    char txt[32]; txt[0] = 0;                                           //Buffer for texdt from ROM.
    {
      const char* txtP = (char*) (FLRp (p + OF_TXT));                   //Get the Flash text pointer.
      if (txtP) STRNCPY (txt, txtP, 32);                                //Copy flash to RAM.
    }
    uint16_t fS = FRwu (p + OF_FS);
    uint16_t tX0 = FRwu (p + OF_TX0);
    uint16_t tX1 = FRwu (p + OF_TX1);
    uint16_t tY0 = FRwu (p + OF_TY0);
    uint16_t tY1 = FRwu (p + OF_TY1);
    int8_t oHiPct = FRbs (p + OF_HIP);
    int8_t oLoPct = FRbs (p + OF_LOP);
    switch (FRwu (p + OF_OBJ))                                          //sd[obj].obj)
    {
    case BUTTON_SLH:        if (   (oW >  4)
                                && (oH > 19))
                            {
                              tft.drawFastHLine (tX0, tY0 + (oH >> 1), tX1 - tX0, oC);
                              tft.fillRect (oX0, oY0, oW, oH, oC);
                            }
                            break;
    case BUTTON_SLV:        if (   (oW > 19)
                                && (oH >  4))
                            {
                              tft.drawFastVLine (tX0 + (oW >> 1), tY0, tY1 - tY0, oC);
                              tft.fillRect (oX0, oY0, oW, oH, oC);
                            }
                            break;
    case BUTTON_RECT:       if (   (oW > 5)
                                && (oH > 9))
                            {
                              tft.fillRect (oX0, oY0, oW, oH, oC);
                              if (!lx0) lx0 = oX0 + ((strlen (txt) * FONT_WIDTH * fS) >> 1);//Half the text width in pixels.
                              if (!ly0) ly0 = oY0 + ((oH - (FONT_HEIGHT * fS)) >> 1) + (FONT_HEIGHT * fS);
                              if (   (oE)
                                  && (oE < (oH >> 1))
                                  && (oE < (oW >> 1)))
                              {
                                uint16_t t = oC >> 1;                   //Make all colors more dark: divide all by 2; clear color shifts.
                                t &= 0x7BEF;                            //0b0111.1011.1110.1111 (from R to G and G to B).
                                tft.fillRect (oX0 + oW - oE, oY0, oE, oH, t);
                                tft.fillRect (oX0, oY0 + oH - oE, oW, oE, t);
                              }
                            }
                            break;
    case BUTTON_RECT_RND:   if (   (oW > 9)
                                && (oH > 9))
                            {
                              tft.fillRoundRect (oX0, oY0, oW, oH, oE, oC);
                              uint16_t fS = FRwu (p + OF_FS);
                              if (!lx0) lx0 = oX0 + ((strlen (txt) * FONT_WIDTH * fS) >> 1);//Half the text width in pixels.
                              if (!ly0) ly0 = oY0 + ((oH - (FONT_HEIGHT * fS)) >> 1) + (FONT_HEIGHT * fS);
                            }
                            break;
    case BUTTON_ROUND:      if (oW > 9)
                            {                                           //Does not use syh (Shape Y Height).
                              uint16_t hh = oY0 + (oW >> 1);
                              tft.fillCircle (oX0 + (oW >> 1), hh, oW >> 1, oC);//Width to radius.
                              if (!ly0) ly0 = hh + ((FONT_HEIGHT * fS) >> 1);//Half Font Height.
                              if (!lx0)
                              {
                                lx0 = FRwu (p + OF_OW) >> 1;            //50% Width/
                                hh = (strlen (txt) * FONT_WIDTH * fS) >> 1;//Half Text Width.
                                lx0 += oX0;                             //Object Left.
                                if (lx0 > hh) lx0 -= hh;
                              }
                            }
                            break;
    case CHECKBOX:          if (   (tX1 > (tX0 + 5))
                                && (tY1 > (tY0 + 5)))
                            {
                              uint8_t i = CHECKBOX_THICK;               //Make the marker a bit wider.
                              do
                              {
                                tft.drawRect (oX0 + i, oY0 + i, oW - (i << 1), oH - (i << 1), oC);//Shape X, Y, W, H, Color.
                                --i;
                              } while (i);
                              if (!lx0) lx0 = oX0 + ((strlen (txt) * FONT_WIDTH * fS) >> 1);//Half the text width in pixels.
                              if (!ly0) ly0 = oY0 + ((oH - (FONT_HEIGHT * fS)) >> 1) + (FONT_HEIGHT * fS);
                            }
                            break;
    case INPUT_DATA:        input_object_set (&sd[obj]);                //Remind the object containing parameters for the displayed user input.
                            input_buffer_copy (txt);
                            break;
    case LABEL:                                                         //Nothing extra to do: coordinates have been fetched and text is printed later.
                            break;
    case LINE:              tft.drawLine (oX0, oY0, oW, oH, oC);
                            break;
    case LINE_H:            tft.drawFastHLine (oX0, oY0, oW, oC);
                            break;
    case LINE_V:            tft.drawFastVLine (oX0, oY0, oH, oC);
                            break;
    case RECTANGLE:         if (   (oW >= 5)
                                && (oH >= 5))
                            {
                              tft.drawRect (oX0, oY0, oW, oH, oC);
                              if (!lx0) lx0 = oX0 + ((strlen (txt) * FONT_WIDTH * fS) >> 1);//Half the text width in pixels.
                              if (!ly0) ly0 = oY0 + ((oH - (FONT_HEIGHT * fS)) >> 1) + (FONT_HEIGHT * fS);
                            }
                            break;
    case RECTANGLE_FILL:    if (   (oW >= 5)
                                && (oH >= 5))
                            {
                              tft.fillRect (oX0, oY0, oW, oH, oC);
                              if (!lx0) lx0 = oX0 + ((strlen (txt) * FONT_WIDTH * fS) >> 1);//Half the text width in pixels.
                              if (!ly0) ly0 = oY0 + ((oH - (FONT_HEIGHT * fS)) >> 1) + (FONT_HEIGHT * fS);
                            }
                            break;
  //#if !defined(__AVR_ATmega328P__) && !defined(__AVR_ATmega328PB__)
      case SLIDERBAR_H:     if (   (oW >= 20)
                                && (oH >= 5)
                                && (oHiPct > oLoPct))
                            {
                              tft.drawRect (oX0, oY0, oW, oH, oC);
                              uint8_t divPos = 0;
                              uint16_t div = oX0;
                              uint16_t step = oW / oE;
                              if (oE) while  (divPos <= oE)
                              {
                                tft.drawFastVLine (div, oY0 - 10, 10, oC);//This can overflow the slider bar if not well divided.
                                div += step;
                                ++divPos;
                              }
                            }
                            break;
      case SLIDERBAR_V:     if (   (oW >= 5)
                                && (oH >= 20)
                                && (oHiPct > oLoPct))
                            {
                              tft.drawRect (oX0, oY0, oW, oH, oC);
                              uint8_t divPos = 0;
                              uint16_t div = oY0;
                              uint16_t step = oH / oE;
                              if (oE) while  (divPos <= oE)
                              {
                                tft.drawFastHLine (oX0 - 10, div, 10, oC);//This can overflow the slider bar if not well divided.
                                div += step;
                                ++divPos;
                              }
                            }
                            break;
  //#endif
    default:                break;
    }
    if (   (lx0)                                                        //Text @ Position 0,0 is not allowed.
        && (ly0)
        && (strlen (txt)))                                              //And Text must contain something.
    {
      uint16_t lC = FRwu (p + OF_LC);
      tft.setCursor (lx0, ly0);
      tft.setTextSize (fS);
      tft.setTextColor (lC);
      tft.print (txt);
    }
    #if defined (TOUCH_HIGHLIGHT)
      for (uint8_t obj = 0; obj < count; ++obj)
      {
        uint16_t tX0 = FRwu (p + OF_TX0);
        uint16_t tX1 = FRwu (p + OF_TX1);
        uint16_t tY0 = FRwu (p + OF_TY0);
        uint16_t tY1 = FRwu (p + OF_TY1);
        tft.drawRect (tX0 - 1, tY0 - 1, tX1 - tX0 + 2, tY1 - tY0 + 2, TOUCH_HIGHLIGHT);
        tft.drawRect (tX0 - 2, tY0 - 2, tX1 - tX0 + 4, tY1 - tY0 + 4, TOUCH_HIGHLIGHT);
      }
    #endif
  }
}





//#if !defined(__AVR_ATmega328P__) && !defined(__AVR_ATmega328PB__)
  /*****************************************************************************************************************************
   * Draw Horizontal Slider Bar vs. a given % range. A horizontal slider has a valid range of (Lower) -128% to (Higher) +125%.
   * Screen View:
   *              tX0, tY0             xt          oHiPct         - The frame goes from (tX0, tY0) width (oW to right) height (oH down).
   *                    +---------+----+------------+             - The touch area is from (tX0, tY0) to (tX1, tY1).
   *                    |         ||||||            | oW          - The range is tX0 (= oLoPct) to tX1 (= oHiPct).
   *                    +---------+----+------------+ oH          - If oLoPct is negative and oHiPct is positive, a virtual zero xPos0
   *                oLoPct       xPos0            tX1, tY1          is used to draw a bi-directional bar. Otherwise the bar fills left-right.
   *
   * \code
   *    const SCR_DATA* b = &sd[?];                                     //? points to an existing SLIDERBAR_H object.
   *    int8_t  pct = 0;
   *    sliderbar_h_fill_pct (b, 75);                                   //Fills the sliderbar to 75%.
   * \endcode
   * \param oPtr                Must point to a SLIDERBAR_H object.
   * \param pct                 Percentage Fill for the sliderbar (required to determine left or right slider if LoPct is negative and HiPct is positive).
   * \param enXtouch          - TOUCHPOS_REF Use actual xTouch position to determine where to relocate the button object (fastest, only possible by touch operation).
   *                          - TOUCHPOS_CALC (default): compute a virtual touch position (slower, can be used to put the object at a specific position).
   */
  void
  slider_h_fill_pct (const SCR_DATA*       oPtr,                        //Must be a SLIDERBAR_H object.
                     const int8_t          pct,                         //Must always be provided as the % click on the bar (max range -128 to + 127)
                     const TOUCH_REFERENCE enXtouch)                    //If this is non-zero, xTouch is used as the reference instead of pct computed.
  {
    if (!oPtr) return;
    uint8_t* p = (uint8_t*) (oPtr);
    if (FRwu (p + OF_OBJ) != SLIDERBAR_H) return;                       //Refuse NULL Pointers or non-SLIDERBAR_H objects.
    int16_t xt = xTouch;
    int16_t valRange = (int16_t)(FRbs (p + OF_HIP) - FRbs (p + OF_LOP));
    if (enXtouch == TOUCHPOS_CALC)
    {
      if (pct == FRbs (p + OF_LOP)) xt = (int16_t)(FRwu (p + OF_TX0));  //pct = minimal: leftmost position (prevent division by zero).
      else
      {
        xt = pct - FRbs (p + OF_LOP);                                   //Compute the absolute percentage vs. the sLoPct (does not fit in 8bit signed).
        uint16_t pct = (uint16_t)(xt) * FRwu (p + OF_OW);               //Scale percentage on sliderbar as if touched.
        xt = FRbs (p + OF_HIP) - FRbs (p + OF_LOP);                     //The total value range.
        pct /= (uint16_t)(xt);                                          //The relative touch position on the slider.
        xt = pct + FRws (p + OF_TX0);                                   //Absolte X touch on the screen.
      }
    }
    uint16_t xPos0 = FRwu (p + OF_OW);                                  //Compute the Bar Pixel Width * 100 (to avoid losses on next divisions).
    xPos0 *= 100;
    xPos0 /= valRange;                                                  //Compute the Bar Pixel Width * 100 (to avoid losses on next divisions).
    xPos0 *= -FRbs (p + OF_LOP);                                        //Deduct Lower value.
    xPos0 +=  50;                                                       //Round by 0.5 pix.
    xPos0 /= 100;                                                       //Scale down 100 to have pixelcount.
    if (xPos0 > FRwu (p + OF_OW)) xPos0 = 0;                            //Overflow correction.
    int16_t xw;
    if (pct < 0)
    {
      xw = xt - FRws (p + OF_TX0) - 1;                                  //Touch position - XLeftmost.
      if (xw > 0) tft.fillRect (FRwu (p + OF_TX0) + 1, FRwu (p + OF_TY0) + 1, xw, FRwu (p + OF_OH) - 2, screenBgndColor);//Left Inactive Bar (when startpoint negative).
      xw = xPos0 - xt + FRws (p + OF_TX0);                              //Bar is from from 0 point
      if (xw > 0) tft.fillRect (xt, FRwu (p + OF_TY0), xw, FRwu (p + OF_OH), FRwu (p + OF_OC)); //Active Bar.
      if (   (xt < FRws (p + OF_TX1))                                   //There is touch area remaining to the right of the touch point.
          && (FRbs (p + OF_HIP) > 0))                                   //Rightmost value > 0).
      {
        xw = xPos0 - 1;                                                 //Width erased bar at the right of the filled bar.
        if (xw > 0) tft.fillRect (xPos0 + FRwu (p + OF_TX0), FRwu (p + OF_TY0) + 1, xw, FRwu (p + OF_OH) - 2, screenBgndColor);//Positive part inactive.
      }
    }
    else
    {
      if (xt > (FRws (p + OF_TX0) + (int16_t) (xPos0)))                 //The zero point is to the right of the touch point (here we can safely typecast xPos0 to int).
      {                                                                 //(bipolar SLIDERBAR range).
        xw = xPos0 - 1;                                                 //Positive percentage: wipe the area left from the zero point.
        if (xw > 0) tft.fillRect (FRwu (p + OF_TX0) + 1, FRwu (p + OF_TY0) + 1, xw, FRwu (p + OF_OH) - 2, screenBgndColor);
      }
      xw = xt - xPos0 - FRws (p + OF_TX0);                              //Positive %; calculate the length of the positive bar range.
      if (xw > 0) tft.fillRect (FRwu (p + OF_TX0) + xPos0, FRwu (p + OF_TY0), xw, FRwu (p + OF_OH), FRwu (p + OF_OC));
      xw = FRws (p + OF_OW) - xt + FRws (p + OF_TX0) - 1;
      if (xw > 0) tft.fillRect (xt, FRwu (p + OF_TY0) + 1, xw, FRwu (p + OF_OH) - 2, screenBgndColor);//Positive %: the remainder area from xTouch to the maximal value.
    }
  }






  /*****************************************************************************************************************************
   * Draw Vertical Slider Bar vs. a given % range. A Vertical slider has a valid range of (Lower) -128% to (Higher) +125%.
   * Screen View:
   *        tX0, tY0     oW                                       - The frame goes from (tX0, tY0) width (oW to right) height (oH down).
   *              +---+                                           - The touch area is from (tX0, tY0) to (tX1, tY1).
   *              |   |                                           - The range is tY1 (= oLoPct) to tY0 (= oHiPct).
   *              |   |                                           - If oLoPct is negative and oHiPct is positive, a virtual zero yPos0
   *              +---+ yPos0                                       is used to draw a bi-directional bar. Otherwise the bar fills bottom-top.
   *              |---|
   *              |---|
   *              +---+ yt
   *              |   |
   *              |   |
   *              +---+ tX1, tY1  oH
   * \code
   *    const SCR_DATA* b = &sd[?];                                     //? points to an existing SLIDERBAR_H object.
   *    int8_t  pct = 0;
   *    sliderbar_h_fill_pct (b, 75, 1);                                //Fills the sliderbar to 75%.
   * \endcode
   * \param oPtr                Must point to a SLIDERBAR_V object.
   * \param pct                 Percentage Fill for the sliderbar (required to determine left or right slider if LoPct is negative and HiPct is positive).
   * \param enXtouch          - TOUCHPOS_REF Use actual xTouch position to determine where to relocate the button object (fastest, only possible by touch operation).
   *                          - TOUCHPOS_CALC (default): compute a virtual touch position (slower, can be used to put the object at a specific position).
   */
  void
  slider_v_fill_pct (const SCR_DATA*       oPtr,                        //Must be a SLIDERBAR_H object.
                     const int8_t          pct,                         //Must always be provided as the % click on the bar (max range -128 to + 127)
                     const TOUCH_REFERENCE enYtouch)                    //If this is non-zero, xTouch is used as the reference instead of pct computed.
  {
    if (!oPtr) return;
    uint8_t* p = (uint8_t*) (oPtr);
    if (FRwu (p + OF_OBJ) != SLIDERBAR_V) return;                       //Refuse NULL Pointers or non-SLIDERBAR_H objects.
    int16_t yt = yTouch;
    int16_t valRange = (int16_t)(FRbs (p + OF_HIP) - FRbs (p + OF_LOP));
    if (enYtouch == TOUCHPOS_CALC)
    {
      if (pct == FRbs (p + OF_LOP)) yt = FRws (p + OF_TY1);             //pct = minimal: leftmost position (prevent division by zero).
      else
      {
        yt = pct - FRbs (p + OF_LOP);                                   //Compute the absolute percentage vs. the sLoPct (does not fit in 8bit signed).
        uint16_t pct = (uint16_t)(yt) * FRwu (p + OF_OH);               //Scale percentage on sliderbar as if touched.
        yt = FRbs (p + OF_HIP) - FRbs (p + OF_LOP);                     //The total value range.
        pct /= (uint16_t)(yt);                                          //The relative touch position on the slider.
        yt = FRws (p + OF_TY1) - pct;                                   //Absolte Y touch on the screen (computed from pct).
      }
    }
    uint16_t yPos0 = (FRwu (p + OF_OH) * 100) / valRange;               //Compute the Bar Pixel Height * 100 (to avoid losses on next divisions).
    yPos0 *= -FRbs (p + OF_LOP);                                        //Deduct Lower value.
    yPos0 +=  50;                                                       //Round by 0.5 pix.
    yPos0 /= 100;                                                       //Scale down 100: pixelcount above tY1 in SLIDERBAR_V object.
    int16_t yh;
    if (pct < 0)                                                        //Negative Percentage.
    {
      yh = FRws (p + OF_TY1) - yt - 2;                                  //Height from touch point to bottom.
      if (yh > 0) tft.fillRect (FRwu (p + OF_TX0) + 1, yt + 1, FRwu (p + OF_OW) - 2, yh, screenBgndColor);//Erase from touch point to Y1.
      yh = yt - yPos0 - FRws (p + OF_TY0);
      if (yh > 0) tft.fillRect (FRwu (p + OF_TX0), FRwu (p + OF_TY0) + yPos0, FRwu (p + OF_OW), yh, FRwu (p + OF_OC));//Draw the active part of the bar from yPos0 to touch point.
      yh = (int16_t)(yPos0) - 2;                                        //Clear for bipolar SLIDERBAR the positive half since pct is negative.
      if (yh > 0) tft.fillRect (FRwu (p + OF_TX0) + 1, FRwu (p + OF_TY0) + 1, FRwu (p + OF_OW) - 2, yh, screenBgndColor);
    }
    else                                                                //Positive Percentage.
    {
      if (yPos0 == 0)
      {
        tft.fillRect (FRwu (p + OF_TX0) + 1, FRwu (p + OF_TY0) + 1, FRwu (p + OF_OW) - 2, yt - FRwu (p + OF_TY0), screenBgndColor);
        tft.fillRect (FRwu (p + OF_TX0), yt, FRwu (p + OF_OW), FRwu (p + OF_TY1) - yt, FRwu (p + OF_OC));
      }
      else
      {
        tft.fillRect (FRwu (p + OF_TX0) + 1, FRwu (p + OF_TY0) + yPos0 + 1, FRwu (p + OF_OW) - 2, yPos0 - 2, screenBgndColor);//Zero somehwere in SLIDERBAR_V: clear negative part.
        yh = yPos0 - yt + FRws (p + OF_OY0);
        if (yPos0 == 0) yh = abs (yh);                                  //yPos0 is at the bottom -> only use height.
        if (yh > 0) tft.fillRect (FRwu (p + OF_TX0), yt, FRwu (p + OF_OW), yh, FRwu (p + OF_OC)); //Fill bar from touch to zero point.
        yh = yt - FRws (p + OF_TX0);
        if (yh > 2) tft.fillRect (FRwu (p + OF_TX0) + 1, FRwu (p + OF_TY0), FRwu (p + OF_OW) - 2, yh, screenBgndColor);
      }
    }
  }
//#endif



/*****************************************************************************************************************************
 * \ingroup API
 * This function is created to overcome a problem caused by MCUFRIEND TFT displays: the resistive touch hardware uses some AVR Analog pins
 * to read the pressing position. But the driver does not restore these pins back to output (leaves them as analog input). As a result, the
 * display operation freezes.
 * \param all                 - 1 performs a complete re-initialization.
 *                            - 2 Other only re-init the digital interface pins.
 *                            - Any other value has no effect.
 * \retval                    None
 */
void
tft_mcufriend_init (int8_t initLevel)
{
  if (initLevel == TFT_COMPLETE)
  {
    uint16_t tftID = tft.readID ();
  //DEBUGPRINTLL (("TFT ID = 0x")); DEBUGPRINTLL ((tftID, HEX)); DEBUGPRINTLL ((" Width: ")); DEBUGPRINTLL ((tft.width ())); DEBUGPRINTLL ((" Height: ")); DEBUGPRINTLLN ((tft.height ()));
    if (tftID == 0xD3D3) tftID = 0x9486;                                //Write-only shield.
    tft.begin (tftID);
    tft.setRotation (1);                                                //Landscape - this affects how the touch has to be configured (see \ref touch_scan).
    tft.setFont (&FreeSans9pt7b);
    return;
  }
  if (initLevel == TFT_DIG_IO)
  {
    pinMode (XM, OUTPUT);
    pinMode (YP, OUTPUT);
    pinMode (XP, OUTPUT);
    pinMode (YM, OUTPUT);
  }
}





/*****************************************************************************************************************************
 * \ingroup API
 * \retval                    Index of the Screen Array Element being 'touched'.
 */
uint8_t
touch_index_get ()
{
  return touchIndex;
}


/*****************************************************************************************************************************
 * \ingroup API
 * Scan the touchscreen coordinates and lookup in the actual \ref SCR_DATA array if the X, Y touch coordinates are valid in a specific
 * object. If a valid touch area is found and a function pointer is found, it will call this pointer. For the special objects:
 * Note if 0,0 is not perfectly calibrated, the Touch return from map function may be negative (hex value 0xFFxx).
 * Easist to get the software correct is print the p.x and p.y values for the 4 corners after calibration and map accordingly.
 * \ref BUTTON_SLH, \ref BUTTON_SLV, \ref SLIDERBAR_H, \ref SLIDERBAR_V: additional screen changes are applied.
 * Note: to be checked: we can basically compute the % output for all object in a single function / iteration. To do this,
 * we have to feed in \ref loPct and \ref hiPct, the touch position (X or Y) and range. Note here are slight differences for
 * every object as \ref SLIDERBAR_H has the full range whereas \ref BUTTON_SLV has only \ref tX0 to (\ref tX1 - \ref tW).
 * Such function could than compute the resulting % for all sliders & button-slides.
 * \param                     None
 * \retval                    None
 */
void
touch_scan ()
{
  static uint32_t lastScan = 0;
  uint32_t now = millis ();
  if ((now - lastScan) < 100UL) return;
  static uint8_t  touchStatus = 0;
  const uint8_t   TOUCH_STAT_PROCESSED = 0x80;
  const uint8_t   TOUCH_STAT_RELEASE_CNT = 0x07;
  TSPoint p = ts.getPoint ();
  tft_mcufriend_init (TFT_DIG_IO);

  if (   (p.z < MINPRESSURE)
      || (p.z > MAXPRESSURE))
  {                                                                     //Touch Released: flag next time it's pressed to search for it.
    if ((touchStatus & TOUCH_STAT_RELEASE_CNT) < TOUCH_STAT_RELEASE_CNT)
    {
      ++touchStatus;
      return;
    }
    touchStatus &= ~(TOUCH_STAT_PROCESSED | TOUCH_STAT_RELEASE_CNT);    //Clear status & counter.
    return;
  }
  yTouch = map (p.x, TS_LEFT,  TS_RIGHT, 0, tft.height ());             //Depends ON \ref tft.setRotation setting;
  xTouch = map (p.y,  TS_TOP, TS_BOTTOM, 0, tft.width ());              //Coverts Touch-coordinates to pixel coordinates.
  if (touchStatus & TOUCH_STAT_PROCESSED) return;                       //Touch already processed.
  touchStatus &= ~TOUCH_STAT_RELEASE_CNT;                               //Clear the release counter.
  const SCR_DATA* sd = NULL;
  uint8_t objCount = 0;
  if (screen_ptr_size_get (screenNow, &sd, &objCount) < 0) return;
  if (sd == NULL) return;
  touchIndex = 0;
  while (touchIndex < objCount)
  {
    const SCR_DATA* oPtr = &sd[touchIndex];
    uint8_t* p = (uint8_t*) (oPtr);                                     //Typecast to byte pointer for FLASH access.
    if (   (xTouch >= FRws (p + OF_TX0))                                //Touch X Left.
        && (xTouch <= FRws (p + OF_TX1))                                //Touch X Right.
        && (yTouch >= FRws (p + OF_TY0))                                //Touch Y Top.
        && (yTouch <= FRws (p + OF_TY1)))                               //Touch Y Bottom.
    {
    //DEBUGPRINT (("touch: X: ")); DEBUGPRINT ((xTouch)); DEBUGPRINT ((", Y: ")); DEBUGPRINTN ((yTouch));
      uint8_t allowExec = 0;
      int8_t pct = 0;
      switch (FRwu (p + OF_OBJ))
      {
      case BUTTON_SLH:      {
                              button_sl_h_pct (oPtr, 0, TOUCHPOS_REF);
                              uint16_t xPos0 = xTouch - FRwu (p + OF_TX0);//Relative position (max 480 on actual display).
                              xPos0 *= 100;                             //*100 to reduce int-division losses.
                              xPos0 += 50;                              //Rounding.
                              xPos0 /= (FRwu (p + OF_TX1) - FRwu (p + OF_TX0) -FRwu (p + OF_OW));//Thise slider has different range than SLIDERBAR_H.
                              int16_t valRange = (int16_t)(xPos0) * (int16_t)(FRbs (p + OF_HIP) - FRbs (p + OF_LOP));
                              valRange /= 100;
                              valRange += (int16_t)(FRbs (p + OF_LOP));
                              pct = (int8_t)(valRange);
                              if (pct < FRbs (p + OF_LOP)) pct = FRbs (p + OF_LOP);
                              if (pct > FRbs (p + OF_HIP)) pct = FRbs (p + OF_HIP);
                              allowExec = 0x01;
                            }
                            break;
      case BUTTON_SLV:      {
                              button_sl_v_pct (oPtr, 0, TOUCHPOS_REF);
                              uint16_t yPos0 = FRwu (p + OF_TY1) - yTouch;//Relative position (max 480 on actual display depending on orientation).
                              yPos0 *= 100;                             //*100 to reduce int-division losses.
                              yPos0 += 50;                              //Rounding.
                              yPos0 /= (FRwu (p + OF_TY1) - FRwu (p + OF_TY0) - FRwu (p + OF_OH));//This slider has different range than SLIDERBAR_V.
                              int16_t valRange = (int16_t)(yPos0) * (int16_t)(FRbs (p + OF_HIP) - FRbs (p + OF_LOP));
                              valRange /= 100;
                              valRange += (int16_t)(FRbs (p + OF_LOP));
                              pct = (int8_t)(valRange);
                              if (pct < FRbs (p + OF_LOP)) pct = FRbs (p + OF_LOP);
                              if (pct > FRbs (p + OF_HIP)) pct = FRbs (p + OF_HIP);
                              allowExec = 0x01;
                            }
                            break;
      case BUTTON_RECT:     {
                              uint16_t oE = FRwu (p + OF_OE);
                              uint16_t oH = FRwu (p + OF_OH);
                              uint16_t oW = FRwu (p + OF_OW);
                              if (   (oE)
                                  && (oE < (oH >> 1))
                                  && (oE < (oW >> 1)))
                              {
                                uint16_t oX0 = FRwu (p + OF_OX0);
                                uint16_t oY0 = FRwu (p + OF_OY0);
                                uint16_t oC = FRwu (p + OF_OC);
                                tft.fillRect (oX0 + oW - oE, oY0, oE, oH, oC);
                                tft.fillRect (oX0, oY0 + oH - oE, oW, oE, oC);
                                delay (50);
                                oC >>= 2;                               //Make all colors more dark: divide all by 4 and clear color shifts.
                                oC &= 0x39E7;                           //0b0011.1001.1110.0111
                                tft.fillRect (oX0 + oW - oE, oY0, oE, oH, oC);
                                tft.fillRect (oX0, oY0 + oH - oE, oW, oE, oC);
                              }
                              touchStatus |= TOUCH_STAT_PROCESSED;      //To be determined: if touch move on invalid range is mandatory to recognize new touch, place it here.
                              allowExec = 0x01;
                              pct = (uint8_t)(touchIndex);
                            }
                            break;
   //#if !defined(__AVR_ATmega328P__) && !defined(__AVR_ATmega328PB__)
      case SLIDERBAR_H:     {
                              uint16_t xPos0 = xTouch - FRwu (p + OF_TX0);//Relative position (max 480 on actual display).
                              xPos0 *= 100;                             //*100 to reduce int-division losses.
                              xPos0 += 50;                              //Rounding.
                              xPos0 /= FRwu (p + OF_OW);
                              int16_t valRange = (int16_t)(xPos0) * (int16_t)(FRbs (p + OF_HIP) - FRbs (p + OF_LOP));
                              valRange /= 100;
                              valRange += (int16_t)(FRbs (p + OF_LOP));
                              pct = (int8_t)(valRange);
                              if (pct < FRbs (p + OF_LOP)) pct = FRbs (p + OF_LOP);
                              if (pct > FRbs (p + OF_HIP)) pct = FRbs (p + OF_HIP);
                              slider_h_fill_pct (oPtr, pct, TOUCHPOS_REF);
                              allowExec = 0x01;
                            }
                            break;
      case SLIDERBAR_V:     {
                              uint16_t yPos0 = FRwu (p + OF_TY1) - yTouch;//Relative position (max 480 on actual display depending on orientation).
                              yPos0 *= 100;                             //*100 to reduce int-division losses.
                              yPos0 /= FRwu (p + OF_OH);
                              int16_t valRange = (int16_t)(yPos0) * (int16_t)(FRbs (p + OF_HIP) - FRbs (p + OF_LOP));
                              valRange /= 100;
                              valRange += (int16_t)(FRbs (p + OF_LOP));
                              pct = (int8_t)(valRange);
                              if (pct < FRbs (p + OF_LOP)) pct = FRbs (p + OF_LOP);
                              if (pct > FRbs (p + OF_HIP)) pct = FRbs (p + OF_HIP);
                              slider_v_fill_pct (oPtr, pct, TOUCHPOS_REF);
                              allowExec = 0x01;
                            }
                            break;
   //#endif
      default:              {
                              touchStatus |= TOUCH_STAT_PROCESSED;      //To be determined: if touch move on invalid range is mandatory to recognize new touch, place it here.
                              allowExec = 0x01;
                              pct = (uint8_t)(touchIndex);
                            }
                            break;
      }
      if (allowExec)
      {
        func_ptr_i8_t f = FLRfp (p + OF_OCL);                           //FP read is 16 or 32bit, depending on FLASH memory size.
        if (f) f (pct);
      }
    }
    ++touchIndex;
  }
}










