KurtE / ILI9341_t3n

Extended ILI9341_T3 library (Teensy) including all SPI buses, Frame buffer, plus
MIT License
50 stars 23 forks source link

Single-character text not being centered properly? #38

Closed paynterf closed 3 years ago

paynterf commented 3 years ago

I have a clock project using the ILI9341 display, and I use the touch sensitive feature of the display to enable a 'clock setting' mode where the user has "+", "-", and "NEXT" buttons available to facilitate setting the time and date.

The "+", "-", and "NEXT" buttons are simple colored rectangles, with '+', '-' and 'NEXT' centered within the rectangles. However, I have found that even using the new centering version of the 'setCursor()' function, the single-character text isn't properly centered in the relevant rectangles, but the multi-character string "NEXT" does seem to be properly centered.

I have attached a photo showing the issue:

IMG_4076

Here is the code used to produce this result (.ino file and required CustomBox.h)

/*
    Name:       Teensy_LCDClockV4.ino
    Created:    3/9/2021 11:12:31 AM
    Author:     FRANKNEWXPS15\Frank

    This project was cloned from Teensy_LCDClockV3 to re-investigate the use of proportional fonts
*/

#include <XPT2046_Touchscreen.h>
#include <ILI9341_t3n.h>
#include <Wire.h>
#include "RTClib.h"
#include "elapsedMillis.h"
#include "CustomBox.h"
#include "ili9341_t3n_font_Arial.h"

#define N0_DIAGNOSTICS
//****************************************************************************
// Settings and objects
//****************************************************************************

#pragma region DISPLAY_PIN_ASSIGNMENTS
//02/15/21 pin assignments from https://www.pjrc.com/store/display_ili9341_touch.html
#define TFT_DC  9
#define TFT_CS 10
#define TFT_RST 7
#define TFT_SCK 13
#define TFT_MISO 12
#define TFT_MOSI 11
#define TOUCH_CS  8
#pragma endregion DISPLAY_PIN_ASSIGNMENTS

XPT2046_Touchscreen ts(TOUCH_CS);
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCK, TFT_MISO);
elapsedMillis mSecSinceLastClockUpdate;
const int CLOCK_UPDATE_MSEC = 1000;

#pragma region DISP_AND_ADJ_BOX_PARAMETERS
// Size of the color selection boxes and the paintbrush size
#define MAX_DISPLAY_X 240
#define MAX_DISPLAY_Y 320
#define SCREEN_ORIENTATION_1

#define TS_MINX 600
#define TS_MINY 600
#define TS_MAXX 3758
#define TS_MAXY 3612

const int PLUSBOX_X = 20;
const int PLUSBOX_Y = 180;
const int PLUSBOX_WIDTH = 50;
const int PLUSBOX_HEIGHT = 50;
const int PLUSBOX_COLOR = ILI9341_BLUE;

const int MINUSBOX_X = 120;
const int MINUSBOX_Y = 180;
const int MINUSBOX_WIDTH = 50;
const int MINUSBOX_HEIGHT = 50;
const int MINUSBOX_COLOR = ILI9341_YELLOW;

const String nextstr = "NEXT";
const int nextstr_width = 70;
const int nextstr_height = 20;
const int NEXTBOX_X = 220;
const int NEXTBOX_Y = 180;
const int NEXTBOX_COLOR = ILI9341_GREEN;
const int NEXTBOX_WIDTH = 100;
const int NEXTBOX_HEIGHT = 50;

const int TIME_BOX_X = 0;
const int TIME_BOX_Y = 82;
const int TIME_BOX_WIDTH = 350;
const int TIME_BOX_HEIGHT = 80;

const int DATE_BOX_X = 0;
const int DATE_BOX_Y = 0;
const int DATE_BOX_WIDTH = 320;
const int DATE_BOX_HEIGHT = 30;

//03/05/21 used to hide adjustment boxes
const int ALL_ADJ_BOXES_WIDTH = NEXTBOX_X + NEXTBOX_WIDTH;
const int ALL_ADJ_BOXES_HEIGHT = 50;

const int TIME_HIGHLIGHT_BOX_WIDTH = 75;
const int TIME_HIGHLIGHT_BOX_HEIGHT = 5;
const int TIME_HIGHLIGHT_BOX_YLOC = 75;
const int ALL_TIME_HIGHLIGHT_BOX_WIDTH = ALL_ADJ_BOXES_WIDTH;

const int DATE_HIGHLIGHT_BOX_WIDTH = 40;
const int DATE_HIGHLIGHT_BOX_HEIGHT = 5;
const int DATE_HIGHLIGHT_BOX_YLOC = 40;
const int ALL_DATE_HIGHLIGHT_BOX_WIDTH = ALL_ADJ_BOXES_WIDTH;

CustomBox PlusBox(PLUSBOX_X, PLUSBOX_Y, PLUSBOX_WIDTH, PLUSBOX_HEIGHT, PLUSBOX_COLOR);
CustomBox MinusBox(MINUSBOX_X, MINUSBOX_Y, MINUSBOX_WIDTH, MINUSBOX_HEIGHT, MINUSBOX_COLOR);
CustomBox NextBox(NEXTBOX_X, NEXTBOX_Y, NEXTBOX_WIDTH, NEXTBOX_HEIGHT, NEXTBOX_COLOR);

CustomBox HourHighlightBox(20, TIME_HIGHLIGHT_BOX_YLOC, TIME_HIGHLIGHT_BOX_WIDTH, TIME_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);
CustomBox MinuteHighlightBox(120, TIME_HIGHLIGHT_BOX_YLOC, TIME_HIGHLIGHT_BOX_WIDTH, TIME_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);
CustomBox SecondHighlightBox(230, TIME_HIGHLIGHT_BOX_YLOC, TIME_HIGHLIGHT_BOX_WIDTH, TIME_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);

CustomBox MonthHighlightBox(95, DATE_HIGHLIGHT_BOX_YLOC, DATE_HIGHLIGHT_BOX_WIDTH, DATE_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);
CustomBox DayHighlightBox(155, DATE_HIGHLIGHT_BOX_YLOC, DATE_HIGHLIGHT_BOX_WIDTH, DATE_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);
CustomBox YearHighlightBox(205, DATE_HIGHLIGHT_BOX_YLOC, 2 * DATE_HIGHLIGHT_BOX_WIDTH, DATE_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);

CustomBox TimeBox(TIME_BOX_X, TIME_BOX_Y, TIME_BOX_WIDTH, TIME_BOX_HEIGHT, ILI9341_BLACK);
//CustomBox TimeBox(TIME_BOX_X, TIME_BOX_Y, TIME_BOX_WIDTH, TIME_BOX_HEIGHT, ILI9341_YELLOW);
CustomBox DateBox(DATE_BOX_X, DATE_BOX_Y, DATE_BOX_WIDTH, DATE_BOX_HEIGHT, ILI9341_BLACK);

enum AdjustmentState
{
  ADJ_NONE = 0,
  ADJ_HOUR,
  ADJ_MIN,
  ADJ_SEC,
  ADJ_DAYOFWEEK,
  ADJ_MONTH,
  ADJ_DAY,
  ADJ_YEAR
};

int aDaysInMonth[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };

AdjustmentState CurAdjState = ADJ_NONE;

const int TOUCH_TIMEOUT_MSEC = 5000; //reverts to regular display if no further touch input
elapsedMillis mSecSinceLastTouch; //timer for touch timout
#pragma endregion Display & Adj Box Parameters

#pragma region RTC Support
#define FORCE_RTC_TO_LAST_COMPILE_TIME //uncomment to manually set RTC to last compile time

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
DateTime now, lastTime;
char buffer[100];
int lasthour = 0; //02/15/21 added to refresh screen on the hour

struct tStamp
{
  unsigned long mSec;
  byte RTC_Status;
};

#define DS3231_ADDRESS 0x68   ///< I2C address for DS3231
#define DS3231_STATUSREG 0x0F ///< Status register

#define RTCStatArraySize 10
#define RTCStatTime_IntervalMsec 100
tStamp RTCStatArray[RTCStatArraySize];

#pragma endregion RTC Support

void setup()
{
  Serial.begin(9600);
  delay(3000);

  Serial.println("Teensy 3.2 TFT Clock Program");

#pragma region DISPLAY INITIALIZATION
  Serial.println("Initializing TFT display");

  tft.begin();
  // Note: you can now set the SPI speed to any value
  // the default value is 30Mhz, but most ILI9341 displays
  // can handle at least 60Mhz and as much as 100Mhz
  //  tft.setClock(60000000);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.setRotation(1);
#pragma endregion region DISPLAY INITIALIZATION

#pragma region TFT DIAGNOSTICS
#ifndef N0_DIAGNOSTICS

  // read diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDSELFDIAG);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);

  Serial.println(F("Benchmark                Time (microseconds)"));

  Serial.print(F("Screen fill              "));
  Serial.println(testFillScreen());
  delay(200);

  Serial.print(F("Text                     "));
  Serial.println(testText());
  delay(600);

  Serial.print(F("Proportional Text        "));
  Serial.println(testProportionalText());
  delay(600);

  Serial.print(F("Lines                    "));
  Serial.println(testLines(ILI9341_CYAN));
  delay(200);

  Serial.print(F("Horiz/Vert Lines         "));
  Serial.println(testFastLines(ILI9341_RED, ILI9341_BLUE));
  delay(200);

  Serial.print(F("Rectangles (outline)     "));
  Serial.println(testRects(ILI9341_GREEN));
  delay(200);

  Serial.print(F("Rectangles (filled)      "));
  Serial.println(testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA));
  delay(200);

  Serial.print(F("Circles (filled)         "));
  Serial.println(testFilledCircles(10, ILI9341_MAGENTA));

  Serial.print(F("Circles (outline)        "));
  Serial.println(testCircles(10, ILI9341_WHITE));
  delay(200);

  Serial.print(F("Triangles (outline)      "));
  Serial.println(testTriangles());
  delay(200);

  Serial.print(F("Triangles (filled)       "));
  Serial.println(testFilledTriangles());
  delay(200);

  Serial.print(F("Rounded rects (outline)  "));
  Serial.println(testRoundRects());
  delay(200);

  Serial.print(F("Rounded rects (filled)   "));
  Serial.println(testFilledRoundRects());
  delay(200);

  Serial.println(F("Done!"));
#endif // !N0_DIAGNOSTICS
#pragma endregion region TFT DIAGNOSTICS

#pragma region RTC_SETUP
  DateBox.Draw(&tft); //erase previous text
  tft.setCursor(0, 0);
  Serial.println("Initializing RTC...");
  tft.println("Initializing RTC...");
  delay(1000);

  if (!rtc.begin())
  {
    DateBox.Draw(&tft); //erase previous text
    tft.setCursor(0, 0);
    Serial.println("Couldn't find RTC");
    tft.println("Couldn't find RTC");
    while (1);
  }

  bool lp = rtc.lostPower();
  Serial.print("lostPower() reports "); Serial.println(lp);
  DateBox.Draw(&tft); //erase previous text
  tft.setCursor(0, 0);
  tft.printf("lostPower() = %d\n", lp);
  delay(1000);
  if (rtc.lostPower())
  {
    Serial.println("RTC lost power.  Setting RTC to last compile time");
    DateBox.Draw(&tft); //erase previous text
    tft.setCursor(0, 0);
    tft.println("RTC lost power.  Setting RTC to last compile time");

    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

#ifdef FORCE_RTC_TO_LAST_COMPILE_TIME
  Serial.println("Forcing RTC to last compile time");
  // following line sets the RTC to the date & time this sketch was compiled
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
#endif // 

  //DateTime now = rtc.now();
  now = rtc.now();
  //char buffer[100];
  memset(buffer, '\0', 100);
  GetDayDateTimeStringFromDateTime(now, buffer);
  Serial.println("Retrieving Date/Time from RTC....");
  Serial.print("Date String = "); Serial.println(buffer);
#pragma endregion RTC_SETUP

  //DEBUG!!
    //tft.println("Setting time to just before midnight");
    //rtc.adjust(DateTime(2021, 02, 13, 00, 59, 45));
  //DEBUG!!

    //02/15/21 needed detect hour changes for screen refresh
  now = rtc.now();
  lasthour = now.hour();
  mSecSinceLastClockUpdate = 0; //used to space out clock updates in loop()
}

void loop(void)
{

  //Serial.printf("%lu: mSecSinceLastClockUpdate = %lu\n", millis(), (long int)mSecSinceLastClockUpdate);
  delay(100);

  //see if there is a touch anywhere
  if (ts.touched())
  {
    mSecSinceLastTouch = 0; //reset the timeout watchdog

    while (mSecSinceLastTouch < TOUCH_TIMEOUT_MSEC)
    {
      //Serial.printf("mSecSinceLastTouch = %lu\n", (unsigned long int)mSecSinceLastTouch);

      TS_Point Tp = ts.getPoint();// Retrieve the touch point
      if (Tp.z > 1000)
      {
        mSecSinceLastTouch = 0; //reset the timeout watchdog
        TS_Point Dp = GetTouchPointInDisplayCoords(Tp);

        CurAdjState = (AdjustmentState)AdjustTimeAndDate(Dp);
        //Serial.printf("Tp.z = %d: AdjustTimeAndDate(%d, %d) returns %d\n", Tp.z, Dp.x, Dp.y, (int)CurAdjState);

        if (CurAdjState > ADJ_NONE)
        {
          ShowLongTimeDisplay();
          ShowAdjButtons();
        }
      }
    }

    HideButtons();
    HideHighlightBars();
    ShowNormalTimeDisplay();
    ShowDateDisplay();
    CurAdjState = ADJ_NONE;

    mSecSinceLastClockUpdate = 0;
  }

  if (mSecSinceLastClockUpdate > CLOCK_UPDATE_MSEC)
  {
    mSecSinceLastClockUpdate -= CLOCK_UPDATE_MSEC;
    now = rtc.now();

    ShowDateDisplay(); //Display day and date

    //Display time
    if (CurAdjState > ADJ_NONE)
    {
      ShowLongTimeDisplay();
    }
    else
    {
      ShowNormalTimeDisplay();
    }
  }
}

#pragma region DATE_TIME_FUNCTIONS
void GetDayDateTimeStringFromDateTime(DateTime dt, char* bufptr)
{
  int mydayofweek = dt.dayOfTheWeek();
  //mydayofweek = (mydayofweek < 0) ? 0 : mydayofweek; //guard for return of 0 from weekday()
  int myday = dt.day();
  int mymonth = dt.month();
  int myyear = dt.year();
  int myhour = dt.hour();
  int mymin = dt.minute();
  int mysec = dt.second();
  char* dayofweek = (char*)daysOfTheWeek[mydayofweek];

  sprintf(bufptr, "%s %4d/%02d/%02d at %02d:%02d:%02d", dayofweek, mymonth, myday, myyear, myhour, mymin, mysec);
}

void HourStringFromDateTime(DateTime dt, char* bufptr)
{
  int hourval = dt.hour();
  if (hourval > 12)
  {
    hourval -= 12;
  }

  //sprintf(bufptr, "%02d", hourval);
  sprintf(bufptr, "%d", hourval);
}

void MinuteStringFromDateTime(DateTime dt, char* bufptr)
{
  sprintf(bufptr, "%02d", dt.minute());
}
#pragma endregion DATE_TIME_FUNCTIONS

AdjustmentState AdjustTimeAndDate(TS_Point Dp)
{
  AdjustmentState adjstate = ADJ_NONE;
  DateTime newtime;
  DateTime now = rtc.now();
  //int new_hour, new_minute, new_month, new_day, new_year;
  int new_hour, new_minute, new_month, new_day;

  //Serial.printf("In GetCurAdjState with CurAdjState = %d, at point (%d,%d)\n", CurAdjState, Dp.x, Dp.y);

  switch (CurAdjState)
  {
  case ADJ_NONE:
    if (TimeBox.TouchHit(Dp.x, Dp.y))
    {
      adjstate = ADJ_HOUR; //always start with hour
      ShowHighlightBar(ADJ_HOUR);
    }
    else if (DateBox.TouchHit(Dp.x, Dp.y))
    {
      adjstate = ADJ_MONTH; //always start with month
      ShowHighlightBar(ADJ_MONTH);
    }
    break;
  case ADJ_HOUR:
    Serial.print("In GetCurAdjState ADJ_HOUR case\n");
    adjstate = ADJ_HOUR;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_hour = now.hour() + 1;
      if (new_hour >= 24)
      {
        new_hour -= 24;
      }
      newtime = DateTime(now.year(), now.month(), now.day(), new_hour, now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_hour = now.hour() - 1;
      if (new_hour < 0)
      {
        new_hour += 24;
      }
      newtime = DateTime(now.year(), now.month(), now.day(), new_hour, now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_MIN;
      ShowHighlightBar(ADJ_MIN);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_MIN:
    Serial.print("In GetCurAdjState ADJ_MIN case\n");
    adjstate = ADJ_MIN;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_minute = now.minute() + 1;
      if (new_minute >= 60)
      {
        new_minute = 0;
      }

      newtime = DateTime(now.year(), now.month(), now.day(), now.hour(), new_minute, now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      newtime = DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute() - 1);
      Serial.printf("ADJ_HOUR case in DoMinusOp().  Decrementing Hour Value\n");
      Serial.printf("Old time is %d:%d:%d:%d\n", now.day(), now.month(), now.year(), now.hour());
      Serial.printf("New time is %d:%d:%d:%d\n", newtime.day(), newtime.month(), newtime.year(), newtime.hour());
      rtc.adjust(newtime);
      Serial.printf("RTC now set to %d:%d:%d:%d\n", now.day(), now.month(), now.year(), now.hour());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_SEC;
      ShowHighlightBar(ADJ_SEC);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_SEC:
    Serial.print("In GetCurAdjState ADJ_SEC case\n");
    adjstate = ADJ_SEC;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute(), now.second());

      newtime = DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute(), 0);
      Serial.printf("New time is %d/%d/%d %d:%d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute(), newtime.second());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute(), now.second());

      newtime = DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute(), 0);
      Serial.printf("New time is %d/%d/%d %d:%d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute(), newtime.second());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_HOUR;
      ShowHighlightBar(ADJ_HOUR);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_DAYOFWEEK:
    break;
  case ADJ_MONTH:
    Serial.print("In GetCurAdjState ADJ_MONTH case\n");
    adjstate = ADJ_MONTH;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_month = now.month() + 1;
      if (new_month > 12)
      {
        new_month -= 12;
      }
      newtime = DateTime(now.year(), new_month, now.day(), now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_month = now.month() - 1;
      new_month = (new_month <= 0) ? 12 : new_month;
      newtime = DateTime(now.year(), new_month, now.day(), now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_DAY;
      ShowHighlightBar(ADJ_DAY);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_DAY:
    Serial.print("In GetCurAdjState ADJ_DAY case\n");
    adjstate = ADJ_DAY;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_day = AdjustDayValue(now.month(), now.day() + 1); //not all months have same number of days
      newtime = DateTime(now.year(), now.month(), new_day, now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_day = now.day() - 1;
      new_day = (new_day <= 0) ? aDaysInMonth[now.month() - 1] : new_day;
      newtime = DateTime(now.year(), now.month(), new_day, now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_YEAR;
      ShowHighlightBar(ADJ_YEAR);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_YEAR:
    Serial.print("In GetCurAdjState ADJ_YEAR case\n");
    adjstate = ADJ_YEAR;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      newtime = DateTime(now.year() + 1, now.month(), now.day(), now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      newtime = DateTime(now.year() - 1, now.month(), now.day(), now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_MONTH;
      ShowHighlightBar(ADJ_MONTH);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  default:
    break;
  }

  //Serial.printf("AdjustTimeAndDate(%d, %d) returns %d\n", Dp.x, Dp.y, (int)adjstate);
  return adjstate;
}

#pragma region Helper Functions
TS_Point GetTouchPointInDisplayCoords(TS_Point Tp)
{
  //Purpose:  Retrieve a touch point in display coordinate system
  //Inputs:
  //  Rotation value
  //  Mapping values
  //  TS_Point from touch chip
  //Outputs:
  //  TS_Point object containing touch (X,Y) in display coordinates
  //Plan:
  //  Step1: Retrieve touch point from touch chip
  //  Step2: Map to display coordinates taking rotation into account

//Step1: Retrieve touch point from touch chip
  //TS_Point Tp = ts.getPoint();
  TS_Point Dp;
  //Step2: Map to display coordinates taking rotation into account
#ifdef SCREEN_ORIENTATION_1
  Dp.x = map(Tp.x, TS_MAXX, TS_MINX, 0, tft.width());
  Dp.y = map(Tp.y, TS_MAXY, TS_MINY, 0, tft.height());
#else
#endif

  ////DEBUG!!
  //Serial.printf(" GetTouchPointInDisplayCoords: %lu\t%d\t%d\t%d\t%d\t%d\n",
  //  millis(), Tp.x, Tp.y, Tp.z, Dp.x, Dp.y);
  ////DEBUG!!

  return Dp;
}

void ShowAdjButtons()
{
  HideButtons();
  PlusBox.Draw(&tft, '+', ILI9341_BLACK, Arial_40, true);
  MinusBox.Draw(&tft, '-', ILI9341_BLACK, Arial_40, true);
  NextBox.Draw(&tft, "NEXT", ILI9341_BLACK, Arial_20, true);
}

void HideButtons()
{
  PlusBox.Draw(&tft, ILI9341_BLACK);
  MinusBox.Draw(&tft, ILI9341_BLACK);
  NextBox.Draw(&tft, ILI9341_BLACK);
}

void ShowNormalTimeDisplay()
{
  //Serial.printf("In ShowNormalTimeDisplay\n");
  now = rtc.now();
  int hournum = now.hour();

  //guard against 1259->1300 && 2359->0000 transitions
  if (hournum > 12)
  {
    hournum -= 12;
  }
  else if (hournum == 0)
  {
    hournum += 12;
  }

  hournum = (hournum > 12) ? hournum - 12 : hournum;

  tft.setCursor(0, 100);
  TimeBox.Draw(&tft); //redraws black background

  tft.setTextColor(ILI9341_RED);
  tft.setFont(Arial_72);
  tft.setCursor(160, 120, true);//try the text centering feature
  tft.printf("%2d:%02d", hournum, now.minute());
}

void ShowLongTimeDisplay()
{
  //Serial.printf("In ShowLongTimeDisplay\n");
  now = rtc.now();
  tft.setTextColor(ILI9341_RED);
  tft.setCursor(160, 120, true);//try the text centering feature
  tft.setFont(Arial_60);
  TimeBox.Draw(&tft); //redraws black background

  int hournum = now.hour();

  //guard against 1259->1300 && 2359->0000 transitions
  if (hournum > 12)
  {
    hournum -= 12;
  }
  else if (hournum == 0)
  {
    hournum += 12;
  }

  hournum = (hournum > 12) ? hournum - 12 : hournum;

  tft.printf("%2d:%02d:%02d", hournum, now.minute(), now.second());
}

void ShowHighlightBar(AdjustmentState adjstate)
{
  switch (adjstate)
  {
  case ADJ_NONE:
    Serial.printf("ADJ_NONE case in ShowHighlightBox()\n");
    HideHighlightBars();
    break;
  case ADJ_HOUR:
    HideHighlightBars();
    HourHighlightBox.Draw(&tft);
    break;
  case ADJ_MIN:
    HideHighlightBars();
    MinuteHighlightBox.Draw(&tft);
    break;
  case ADJ_SEC:
    HideHighlightBars();
    SecondHighlightBox.Draw(&tft);
    break;
  case ADJ_DAYOFWEEK:
    break;
  case ADJ_MONTH:
    HideHighlightBars();
    MonthHighlightBox.Draw(&tft);
    break;
  case ADJ_DAY:
    HideHighlightBars();
    DayHighlightBox.Draw(&tft);
    break;
  case ADJ_YEAR:
    HideHighlightBars();
    YearHighlightBox.Draw(&tft);
    break;
  default:
    break;
  }
}

void HideHighlightBars()
{
  tft.fillRect(0, DATE_HIGHLIGHT_BOX_YLOC, ALL_DATE_HIGHLIGHT_BOX_WIDTH, DATE_HIGHLIGHT_BOX_HEIGHT, ILI9341_BLACK);
  tft.fillRect(0, TIME_HIGHLIGHT_BOX_YLOC, ALL_TIME_HIGHLIGHT_BOX_WIDTH, TIME_HIGHLIGHT_BOX_HEIGHT, ILI9341_BLACK);
}

void ShowDateDisplay()
{
  DateTime now = rtc.now();
  DateBox.Draw(&tft);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(0, 0);
  //tft.setTextSize(3);
  tft.setFont(Arial_28);
  tft.printf("%s %02d/%02d/%02d\n",
    daysOfTheWeek[now.dayOfTheWeek()], now.month(), now.day(), now.year());
}

int AdjustDayValue(int month, int day)
{
  if (day > aDaysInMonth[month - 1])
  {
    Serial.printf("%d is greater than max days (%d) for this month - setting to 1\n", day, aDaysInMonth[month - 1]);
    return 1;
  }
  else
  {
    return day;
  }
}
#pragma endregion Helper Functions

And the required 'CustomBox.h' file:

#pragma once

#ifndef _ILI9341_t3NH_  
#include <ILI9341_t3n.h>
#endif // !_ILI9341_t3NH_

class CustomBox
{
public:
    int x;
    int y;
private:
    int w;
    int h;
    int color;
public:
    CustomBox(int in_x, int in_y, int in_w, int in_h, int in_color);
    CustomBox(int in_w, int in_h, int in_color);
    void Draw(ILI9341_t3n* disp);
    void Draw(ILI9341_t3n* disp, char c, int txtcolor, int size);
    void Draw(ILI9341_t3n* disp, char c, int txtcolor, ILI9341_t3_font_t font, bool centered = false);
    void Draw(ILI9341_t3n* disp, String txt, int txtcolor, ILI9341_t3_font_t font, bool centered = false);
    void Draw(ILI9341_t3n* disp, String txt, int size);
    void Draw(ILI9341_t3n* disp, char* buff, int size);
    void Draw(ILI9341_t3n* disp, int color);
    bool TouchHit(int in_x, int in_y);
};

CustomBox::CustomBox(int in_w, int in_h, int in_color)
{
    x = 0;
    y = 0;
    w = in_w;
    h = in_h;
    color = in_color;
}

CustomBox::CustomBox(int in_x, int in_y, int in_w, int in_h, int in_color)
{
    x = in_x;
    y = in_y;
    w = in_w;
    h = in_h;
    color = in_color;
}

inline void CustomBox::Draw(ILI9341_t3n* disp)
{
    disp->fillRect(x, y, w, h, color);
}

inline void CustomBox::Draw(ILI9341_t3n* disp, char c, int txtcolor, ILI9341_t3_font_t font, bool centered)
{
    int txt_x = x;
    int txt_y = y;
    disp->setFont(font);
    if (centered)
    {
        txt_x = x + round((float)(w / 2.));
        txt_y = y + round((float)(h / 2.));
    }

    Serial.printf("txt cursor set to (%d,%d)\n", txt_x, txt_y);
    disp->fillRect(x, y, w, h, color);
    disp->setTextColor(txtcolor);
    disp->setCursor(txt_x, txt_y, centered);
    disp->print(c);
}

inline void CustomBox::Draw(ILI9341_t3n* disp, String txt, int txtcolor, ILI9341_t3_font_t font, bool centered)
{
    int txt_x = x;
    int txt_y = y;
    disp->setFont(font);
    if (centered)
    {
        txt_x = x + round((float)(w / 2.));
        txt_y = y + round((float)(h / 2.));
    }

    Serial.printf("txt cursor set to (%d,%d)\n", txt_x, txt_y);
    disp->fillRect(x, y, w, h, color);
    disp->setTextColor(txtcolor);
    disp->setCursor(txt_x, txt_y, centered);
    disp->print(txt);
}

//class CustomBoxWithText :CustomBox
//{
//private:
//  char boxtext[6];
//public:
//  CustomBoxWithText(int in_x, int in_y, int in_w, int in_h, int in_color, char* txt);
//};
//
//
//CustomBox::CustomBox(int in_x, int in_y, int in_w, int in_h, int in_color)
//{
//  x = in_x;
//  y = in_y;
//  w = in_w;
//  h = in_h;
//  color = in_color;
//}

inline void CustomBox::Draw(ILI9341_t3n* disp, char c, int txtcolor, int size = 0)
{
    disp->fillRect(x, y, w, h, color);
    if (size==0)
    {
        size = round((float)h / 10.);
    }
    int char_x = x + round((float)w / 4.);
    //int char_x = x;
    disp->setCursor(char_x, y+size);
    disp->setTextSize(size);
    disp->setTextColor(txtcolor);
    disp->print(c);
}

inline void CustomBox::Draw(ILI9341_t3n* disp, String txt, int size)
{
    disp->fillRect(x, y, w, h, color);
    disp->setCursor(x, y);
    disp->setTextSize(size);
    disp->print(txt);
}

inline void CustomBox::Draw(ILI9341_t3n* disp, char* buff, int size)
{
    Serial.printf("In Draw() with buff = %s\n", buff);
    disp->fillRect(x, y, w, h, color);
    //disp->setCursor(x, y);
    disp->setTextSize(size);
    //disp->setTextDatum(MC_DATUM);
    //disp->drawString1(buff, strlen(buff), x, y);
    disp->drawString(buff, strlen(buff), x, y); //03/09/21 in latest library version drawString1 was made into an overload of drawString
}

inline void CustomBox::Draw(ILI9341_t3n* disp, int newcolor)
{
    disp->fillRect(x, y, w, h, newcolor);
}

inline bool CustomBox::TouchHit(int in_x, int in_y)
{
    //Purpose:  Determine if input coordinates are within a specified box
    //Inputs:
    //  in_x, in_y = integers denoting mapped touch coordinates
    //  box location & dimensions
    //Outputs:
    //  true if (in_x, in_y) falls within box region.  Othewise, false

    //Serial.printf("TouchHit(%d,%d), box (x,y,w,h) = (%d,%d,%d,%d)\n",
    //  in_x, in_y, x, y, w, h);

    return (in_x > x && in_y > y && in_x < x + w && in_y < y + h);
}
KurtE commented 3 years ago

@paynterf @mjs513 - I think it has a lot more to do with the character - than anything else.

That is if I strip your sketch down to just the drawing and a few things drawn, like:

/*
    Name:       Teensy_LCDClockV4.ino
    Created:  3/9/2021 11:12:31 AM
    Author:     FRANKNEWXPS15\Frank

    This project was cloned from Teensy_LCDClockV3 to re-investigate the use of proportional fonts
*/

#include <XPT2046_Touchscreen.h>
#include <ILI9341_t3n.h>
#include "CustomBox.h"
#include "ili9341_t3n_font_Arial.h"

#define N0_DIAGNOSTICS
//****************************************************************************
// Settings and objects
//****************************************************************************

#pragma region DISPLAY_PIN_ASSIGNMENTS
//02/15/21 pin assignments from https://www.pjrc.com/store/display_ili9341_touch.html
#define TFT_DC  9
#define TFT_CS 7
#define TFT_RST 8
#define TFT_SCK 13
#define TFT_MISO 12
#define TFT_MOSI 11
#define TOUCH_CS  6
#pragma endregion DISPLAY_PIN_ASSIGNMENTS

XPT2046_Touchscreen ts(TOUCH_CS);
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCK, TFT_MISO);
elapsedMillis mSecSinceLastClockUpdate;
const int CLOCK_UPDATE_MSEC = 1000;

#pragma region DISP_AND_ADJ_BOX_PARAMETERS
// Size of the color selection boxes and the paintbrush size
#define MAX_DISPLAY_X 240
#define MAX_DISPLAY_Y 320
#define SCREEN_ORIENTATION_1

#define TS_MINX 600
#define TS_MINY 600
#define TS_MAXX 3758
#define TS_MAXY 3612

const int PLUSBOX_X = 20;
const int PLUSBOX_Y = 180;
const int PLUSBOX_WIDTH = 50;
const int PLUSBOX_HEIGHT = 50;
const int PLUSBOX_COLOR = ILI9341_BLUE;

const int MINUSBOX_X = 120;
const int MINUSBOX_Y = 180;
const int MINUSBOX_WIDTH = 50;
const int MINUSBOX_HEIGHT = 50;
const int MINUSBOX_COLOR = ILI9341_YELLOW;

const String nextstr = "NEXT";
const int nextstr_width = 70;
const int nextstr_height = 20;
const int NEXTBOX_X = 220;
const int NEXTBOX_Y = 180;
const int NEXTBOX_COLOR = ILI9341_GREEN;
const int NEXTBOX_WIDTH = 100;
const int NEXTBOX_HEIGHT = 50;

CustomBox PlusBox(PLUSBOX_X, PLUSBOX_Y, PLUSBOX_WIDTH, PLUSBOX_HEIGHT, PLUSBOX_COLOR);
CustomBox MinusBox(MINUSBOX_X, MINUSBOX_Y, MINUSBOX_WIDTH, MINUSBOX_HEIGHT, MINUSBOX_COLOR);
CustomBox NextBox(NEXTBOX_X, NEXTBOX_Y, NEXTBOX_WIDTH, NEXTBOX_HEIGHT, NEXTBOX_COLOR);
CustomBox Plus2Box(PLUSBOX_X, PLUSBOX_Y-100, PLUSBOX_WIDTH, PLUSBOX_HEIGHT, PLUSBOX_COLOR);
CustomBox Minus2Box(MINUSBOX_X, MINUSBOX_Y-100, MINUSBOX_WIDTH, MINUSBOX_HEIGHT, MINUSBOX_COLOR);

void setup()
{
  Serial.begin(9600);
  delay(3000);

  Serial.println("Teensy 3.2 TFT Clock Program");

  #pragma region DISPLAY INITIALIZATION
  Serial.println("Initializing TFT display");

  tft.begin();
  // Note: you can now set the SPI speed to any value
  // the default value is 30Mhz, but most ILI9341 displays
  // can handle at least 60Mhz and as much as 100Mhz
  //  tft.setClock(60000000);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.setRotation(1);

  tft.setFont(Arial_40);

  int16_t x1, y1, w, h;
  tft.setCursor(50,20);
  tft.getTextBounds("-", 50, 20, &x1, &y1, &w, &h);
  tft.print("-");
  tft.drawRect(x1, y1, w, h, ILI9341_RED);    

  tft.setCursor(100,20);
  tft.getTextBounds("+", 100, 20, &x1, &y1, &w, &h);
  tft.print("+");
  tft.drawRect(x1, y1, w, h, ILI9341_RED);    

  tft.setCursor(150,20);
  tft.getTextBounds("X", 150, 20, &x1, &y1, &w, &h);
  tft.print("X");
  tft.drawRect(x1, y1, w, h, ILI9341_RED);    

  PlusBox.Draw(&tft, '+', ILI9341_BLACK, Arial_40, true);
  MinusBox.Draw(&tft, '-', ILI9341_BLACK, Arial_40, true);
  NextBox.Draw(&tft, "NEXT", ILI9341_BLACK, Arial_20, true);
  Plus2Box.Draw(&tft, "X", ILI9341_BLACK, Arial_40, true);
  Minus2Box.Draw(&tft, "Z", ILI9341_BLACK, Arial_40, true);
}
void loop(void)
{
  //Serial.printf("%lu: mSecSinceLastClockUpdate = %lu\n", millis(), (long int)mSecSinceLastClockUpdate);
}

As you can see the - does not draw in the rectangle returned, like the other two chars do: IMG_1344

paynterf commented 3 years ago

Kurt,

Nice experiment! Any idea why the "-" character misbehaves like this?

TIA,

Frank

paynterf commented 3 years ago

Kurt,

Hmm, I was able to center the '-' character by using the horizontal character extent, but ignoring the vertical extent, as shown in the code snippet below.

  MinusBox.Draw(&tft);
  Serial.printf("In ShowAdjButtons after MinuBox.Draw()\n");
  int16_t x1 = 0;
  int16_t y1 = 0;
  uint16_t w = 0;
  uint16_t h = 0;
  tft.setFont(Arial_40);
  tft.getTextBounds("-", MINUSBOX_X, MINUSBOX_Y, &x1, &y1, &w, &h);
  Serial.printf("In ShowAdjButtons: getTextBounds shows %d,%d,%d,%d\n", x1, y1, w, h);

  int chr_x = MINUSBOX_X + MINUSBOX_WIDTH / 2 - w / 2  ;
  int chr_y = MINUSBOX_Y;
  Serial.printf("Printing char at %d,%d\n", chr_x, chr_y);
  tft.setCursor(chr_x, chr_y);
  tft.setTextColor(ILI9341_BLACK);
  tft.print("-");

IMG_4080

Then I had a thought that maybe the '-' (dash, or minus) character may actually be the ' ' (underscore) character! This would explain the results we are seeing perfectly. Unfortunately when I actually replaced '-' with ' ' the displayed character was considerably lower and slightly more to the right - bummer!

So, there is still some unknown error in the getTextBounds() function that gets excited when dealing with a character with a very small vertical extent, but it may not be worth the time to figure it out. I can get what I want by using getTextBounds() manually as I did above ;-)

It's a mystery!

Frank

KurtE commented 3 years ago

@paynterf @mjs513 Yes that may be true, that it is unclear if we should address it or not: I instrumented up the call to get the bounds

void ILI9341_t3n::charBounds(char c, int16_t *x, int16_t *y, int16_t *minx,
                             int16_t *miny, int16_t *maxx, int16_t *maxy) {

  // BUGBUG:: Not handling offset/clip
  if (font) {
    if (c == '\n') { // Newline?
      *x = 0;        // Reset x to zero, advance y by one line
      *y += font->line_space;
    } else if (c != '\r') { // Not a carriage return; is normal char
      uint32_t bitoffset;
      const uint8_t *data;
      if (c >= font->index1_first && c <= font->index1_last) {
        bitoffset = c - font->index1_first;
        bitoffset *= font->bits_index;
      } else if (c >= font->index2_first && c <= font->index2_last) {
        bitoffset =
            c - font->index2_first + font->index1_last - font->index1_first + 1;
        bitoffset *= font->bits_index;
      } else if (font->unicode) {
        return; // TODO: implement sparse unicode
      } else {
        return;
      }
       Serial.printf("  char = %c index =  %d\n", c, fetchbits_unsigned(font->index,
       bitoffset, font->bits_index));
      data = font->data +
             fetchbits_unsigned(font->index, bitoffset, font->bits_index);

      uint32_t encoding = fetchbits_unsigned(data, 0, 3);
      if (encoding != 0)
        return;
      uint32_t width = fetchbits_unsigned(data, 3, font->bits_width);
      bitoffset = font->bits_width + 3;
      uint32_t height = fetchbits_unsigned(data, bitoffset, font->bits_height);
      bitoffset += font->bits_height;
       Serial.printf("  size =   %d,%d\n", width, height);
       Serial.printf("  line space = %d\n", font->line_space);

      int32_t xoffset = fetchbits_signed(data, bitoffset, font->bits_xoffset);
      bitoffset += font->bits_xoffset;
      int32_t yoffset = fetchbits_signed(data, bitoffset, font->bits_yoffset);
      bitoffset += font->bits_yoffset;
      Serial.printf("  offsets  = x:%d y:%d\n",  xoffset,  yoffset);

      uint32_t delta = fetchbits_unsigned(data, bitoffset, font->bits_delta);
      bitoffset += font->bits_delta;

      int16_t x1 = *x + xoffset, y1 = *y + yoffset, x2 = x1 + width,
              y2 = y1 + height;

      if (wrap && (x2 > _width)) {
        *x = 0; // Reset x to zero, advance y by one line
        *y += font->line_space;
        x1 = *x + xoffset, y1 = *y + yoffset, x2 = x1 + width, y2 = y1 + height;
      }
      if (x1 < *minx)
        *minx = x1;
      if (y1 < *miny)
        *miny = y1;
      if (x2 > *maxx)
        *maxx = x2;
      if (y2 > *maxy)
        *maxy = y2;
      *x += delta; // ? guessing here...
    }
  }

Also output stuff in the function that called this:

  while ((c = *str++)) {
    charBounds(c, &x, &y, &minx, &miny, &maxx, &maxy);
    Serial.printf("%c %d %d - %d %d %d %d\n", c, x, y, minx, miny, maxx, maxy);
  }

And debug output shows:

Teensy 3.2 TFT Clock Program
Initializing TFT display
  char = - index =  837
  size =   16,5
  line space = 61
  offsets  = x:1 y:12
- 69 20 - 51 32 67 37
  char = + index =  803
  size =   27,27
  line space = 61
  offsets  = x:3 y:6
+ 133 20 - 103 26 130 53
  char = X index =  4921
  size =   37,40
  line space = 61
  offsets  = x:0 y:0
X 187 20 - 150 20 187 60

So my guess is that y size being much smaller than the line spacing is not properly handling the y offset... Will take a quick look to see what the difference in the handling of these values are versus when we actually draw the text with them.

paynterf commented 3 years ago

Kurt, Yeah, I'm not sure it's "worth the candle" either, as it seems to only affect single characters with small vertical extents. FWIW, I manually centered the '+' and '-' signs on my boxes, using the following code:

void ShowAdjButtons()
{
  Serial.printf("In ShowAdjButtons\n");
  HideButtons();

  //03/13/21 have to center plus (+) & minus sign (-) manually

  //can't use getTextBounds() at all for '+' sign centering
  PlusBox.Draw(&tft); //draw rectangle with no text
  int chr_x = PLUSBOX_X + PLUSBOX_WIDTH / 2 - 16;
  int chr_y = PLUSBOX_Y + PLUSBOX_HEIGHT / 2 -  20;
  tft.setFont(Arial_40);
  tft.setCursor(chr_x, chr_y);
  tft.setTextColor(ILI9341_BLACK);
  tft.print("+");

  //for '-' sign, get text bounds, but ignore the vertical extent
  MinusBox.Draw(&tft); //draw rectangle with no text
  int16_t x1 = 0;
  int16_t y1 = 0;
  uint16_t w = 0;
  uint16_t h = 0;
  tft.setFont(Arial_40);
  tft.getTextBounds("-", MINUSBOX_X, MINUSBOX_Y, &x1, &y1, &w, &h);

  chr_x = MINUSBOX_X + MINUSBOX_WIDTH / 2 - w / 2  ;
  chr_y = MINUSBOX_Y; //ignore getTextBounds() vertical extent
  tft.setCursor(chr_x, chr_y);
  tft.setTextColor(ILI9341_BLACK);
  tft.print("-");

  //03/13/21 centering feature works OK with regular text
  NextBox.Draw(&tft, "NEXT", ILI9341_BLACK, Arial_20, true);
}

This resulted in the '+' & '-' signs being exactly (as closely as I could measure using a digital caliper) centered in my 9x9mm boxes. Interestingly enough, the vertical and horizontal extents reported by getTextExtents() for the '+' was 28x28, which should have meant that a negative offset of w/2 and h/2 from center should have put the '+' sign in the center of the box, but I actually needed a negative offset of 16 horizontally (2 more than it should have required) and 20 vertically (6 more than it should have required). For the '-' sign I used the horizontal extent w/o modification, and ignored the vertical extent entirely.

IMG_4081

KurtE commented 3 years ago

I think I pushed up a fix to this.

Let me know if works for you

paynterf commented 3 years ago

WOW - Perfectly centered! You are a magician!

Frank