Erster Wurf (formal nicht korrekt)
/*
Initialisierung des TFT Displays
und die Routinen zur Cursordarstellung
2023-03-30
by ec2021
Diese Datei als "TFTCursor.h" im Verzeichnis des
Hauptsketches ablegen und im Hauptsketch durch
#include "TFTCursor.h"
einbinden.
Die Routinen wurden so angepasst, dass sie sowohl mit Systemschrift wie auch Proportional-
schriften aus der ADAFruit-Bibliothek funktionieren. Sie können mit verschiedenen Textgrößen
wie auch unterschiedlicher Display-Rotation verwendet werden.
Bekannte Einschränkung:
Der Text ***muss*** in eine Zeile passen, sonst erscheint der Cursor
an einer falschen Stelle.
Für den Anwender verfügbare Funktionen:
--------------------------------------------------------------------------------------------
void setCursorColor(uint16_t col);
--------------------------------------------------------------------------------------------
Setzen der Cursor-Farbe (kann also von der verwendeten Textfarbe abweichen!)
--------------------------------------------------------------------------------------------
void setbkColor(uint16_t col);
--------------------------------------------------------------------------------------------
Setzen der Hintergrund-Farbe, mit der zuvor geschriebener Text gelöscht wird.
Das "Übermalen" des Textes mit einem Rechteck in Hintergrundfarbe ist bei
Verwendung von Proportionalschrift die sicherste Methode, um Artefakte zu
vermeiden.
--------------------------------------------------------------------------------------------
void print(String aTxt,int atX, int atY, uint16_t col);
--------------------------------------------------------------------------------------------
Gibt den Text an der Stelle atX, atY in der Farbe col aus.
Mit diesem Aufruf werden automatisch alle Parameter gesetzt bzw. ermittelt,
die für das "Unterstreichen" mit dem Cursor erforderlich sind.
Bei erneutem Aufruf von print() wird der vorherigen Text auf dem TFT-Schirm gelöscht.
Dabei wird die Hintergrundfarbe angewandt, Defaultwert ist Schwarz. Weicht die
verwendete Hintergrund davon ab, kann dies mit setBkColor() angepasst werden.
--------------------------------------------------------------------------------------------
void setBlinkTime(unsigned long bTime);
--------------------------------------------------------------------------------------------
Setzen der Blinkzeit in [ms], Default-Wert ist 300 ms
--------------------------------------------------------------------------------------------
void Blink(int Stelle);
--------------------------------------------------------------------------------------------
Diese Funktion läßt den Cursor in einem Takt von 300 ms (default) blinken
Die Blinkgeschwindigkeit kann mit setBlinkTime(); geändert werden.
WICHTIG. Diese Funktion benutzt die millis()-Funktion; sie muss also
in der loop() regelmäßig aufgerufen werden; blockierende Funktionen
behindern das Blinken!
--------------------------------------------------------------------------------------------
void ClearBlink();
--------------------------------------------------------------------------------------------
Um sicherzugehen, dass nach einem Wechsel der Print-Position keine "Reste" zurückbleiben,
empfiehlt es sich, vor einem erneuten Print zuerst ClearBlink() aufzurufen.
*/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#define TFT_DC 9
#define TFT_CS 10
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
struct cDataType {
int gap = 0;
int width = 0;
int height = 0;
};
cDataType getCharData(char aChar){
int16_t x1, y1;
uint16_t w, h;
cDataType c;
if (!aChar) return c;
tft.getTextBounds(String(aChar), 10,180, &x1, &y1, &c.width, &c.height);
tft.getTextBounds(String(aChar)+String(aChar), 10,180, &x1, &y1, &w, &h);
c.gap = (w - 2 * c.width)/2;
return c;
}
struct CursorType {
public :
void print(String aTxt,int atX, int atY, uint16_t col);
void setCursorColor(uint16_t col);
void setBkColor(uint16_t col);
void setBlinkTime(unsigned long bTime);
void Blink(int aPos);
void ClearBlink();
boolean FixedFontInUse = true;
private:
int x;
int y;
String txt = "";
cDataType cData;
int xOffset = 0;
int yPos = 0;
int barHeight = 0;;
int barWidth = 0;
uint16_t color;
uint16_t bkColor = ILI9341_BLACK;
int Position = 0;
unsigned long BlinkTime = 300;
void Draw();
void Delete();
void getOffsets();
void setCursor(int atX, int atY);
};
void CursorType::print(String aTxt,int atX, int atY, uint16_t col){
int16_t x1, y1;
uint16_t w, h;
if (CursorType::txt != "") {
tft.getTextBounds(CursorType::txt, CursorType::x, CursorType::y, &x1, &y1, &w, &h);
tft.fillRect(x1,y1,w,h,CursorType::bkColor);
}
CursorType::txt = aTxt;
CursorType::x = atX;
CursorType::y = atY;
tft.setTextColor(col);
tft.setCursor(CursorType::x, CursorType::y);
tft.print(CursorType::txt);
tft.getTextBounds(CursorType::txt, CursorType::x, CursorType::y, &x1, &y1, &w, &h);
CursorType::yPos = y1+2+h;
}
void CursorType::setCursor(int atX, int atY){
CursorType::x = atX;
CursorType::y = atY;
}
void CursorType::setCursorColor(uint16_t col){
CursorType::color = col;
}
void CursorType::setBkColor(uint16_t col){
CursorType::bkColor = col;
}
void CursorType::setBlinkTime(unsigned long bTime){
CursorType::BlinkTime = bTime;
}
void CursorType::getOffsets(){
int16_t x1, y1;
uint16_t w, h;
int xOff = 0;
char prevC, actC;
cDataType c;
CursorType::barWidth = 3;
CursorType::barHeight = 3;
CursorType::xOffset = 0;
int len = CursorType::txt.length();
if (!len) return;
if (CursorType::Position < 1) CursorType::Position = 1;
if (CursorType::Position > len) CursorType::Position = len;
if (len == 1 || CursorType::Position == 1) {
c = getCharData(CursorType::txt.charAt(0));
xOff = -c.gap/2;
CursorType::barWidth = c.width+ c.gap;
} else {
String sTxt = CursorType::txt.substring(0,CursorType::Position-1);
tft.getTextBounds(sTxt, CursorType::x,CursorType::y, &x1, &y1, &w, &h);
xOff = w + x1 - CursorType::x;
if (CursorType::Position>1) {
prevC = CursorType::txt.charAt(CursorType::Position-2);
c = getCharData(prevC);
xOff += c.gap;
}
actC = CursorType::txt.charAt(CursorType::Position-1);
c = getCharData(actC);
xOff += c.gap;
CursorType::barWidth = c.width + c.gap;
}
CursorType::barHeight = 3;
CursorType::xOffset = xOff;
}
void CursorType::Draw(){
int width;
int xPos;
CursorType::getOffsets();
xPos = CursorType::x + CursorType::xOffset;
tft.fillRect( xPos,
CursorType::yPos,
CursorType::barWidth,
CursorType::barHeight,
CursorType::color);
}
void CursorType::Delete(){
uint16_t oldCol = CursorType::color;
CursorType::color = CursorType::bkColor;
CursorType::Draw();
CursorType::color = oldCol;
}
void CursorType::Blink(int aPos){
static unsigned long lastChange = 0;
static boolean Paint = true;
static int lastPos = 0;
if (lastPos != aPos) {
CursorType::Position = lastPos;
CursorType::Delete();
lastChange = 0;
lastPos = aPos;
Paint = true;
}
if (millis()-lastChange > CursorType::BlinkTime){
CursorType::Position = lastPos;
lastChange = millis();
if (Paint) {
CursorType::Draw();
} else {
CursorType::Delete();
}
Paint = !Paint;
}
}
void CursorType::ClearBlink(){
CursorType::Delete();
}
Beispielprogramm dazu:
/*
Beispiel für
https://forum.arduino.cc/t/tft-input-placeholder/1107597
2023-03-30
ec2021
*/
#include "TFTCursor.h"
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSans24pt7b.h>
#define LEFT 10
#define TOP 100
CursorType myCursor;
int Pos = 1;
unsigned long lastChange = 0;
constexpr int NoOfStrings = 3;
int StrNo = 0;
String txt[NoOfStrings] = {"00:00", "aBcDe", "01:2a3@4"};
void setup() {
Serial.begin(115200);
Serial.println("Start ...");
tft.begin();
tft.setRotation(3);
tft.setFont(&FreeSans24pt7b);
tft.setTextSize(1);
myCursor.setCursorColor(ILI9341_YELLOW);
myCursor.setBlinkTime(300);
myCursor.print(txt[0], LEFT, TOP, ILI9341_RED);
}
void ChangeText() {
Pos = 1;
StrNo++;
if (StrNo >= NoOfStrings) StrNo = 0;
switch (StrNo) {
case 0 : myCursor.print(txt[StrNo], LEFT + 80, TOP, ILI9341_RED);
break;
case 1 : myCursor.print(txt[StrNo], LEFT + 40, TOP + 30, ILI9341_GREEN);
break;
case 2 : myCursor.print(txt[StrNo], LEFT, TOP - 20, ILI9341_BLUE);
break;
default : myCursor.print(txt[StrNo], LEFT, TOP, ILI9341_YELLOW);
}
}
void loop() {
myCursor.Blink(Pos);
if (millis() - lastChange > 1400) {
myCursor.ClearBlink();
lastChange = millis();
Pos++;
if (Pos > txt[StrNo].length()) ChangeText();
}
}
Funktioniert mit Proportionalschrift und Systemfont sowie mit verschiedenen Schriftgrößen und den vier möglichen "Rotationen"...
Siehe hier : https://wokwi.com/projects/360609042954214401
Die Position des Cursors und seine Breite wird über die getTextBounds() errechnet. Das geht vermutlich alles noch etwas eleganter, aber ich bin erstmal zufrieden ... 
Im Hauptsketch braucht man Folgendes;
// Deklaration
CursorType myCursor;
// Vorbereitung
myCursor.setCursorColor(ILI9341_YELLOW);
myCursor.setBlinkTime(300);
// Print des Strings, z.B. txt = "00:00" o.ä.
myCursor.print(txt, LEFT, TOP, ILI9341_RED);
// Regelmäßger Aufruf der Blinkroutine mit Angabe der
// mit Cursor zu unterlegenden Position (1 .. n)
myCursor.Blink(Pos);
// Vor erneutem Aufruf der Print-Routine, hiermit eventuelle
// Reste des Cursors löschen
myCursor.ClearBlink();
Anleitung siehe im oberen File ...