1167 lines
40 KiB
C++
1167 lines
40 KiB
C++
|
|
/**
|
|
* @file gui.ino
|
|
* @brief Graphical User Interface (GUI) for PacoMouseCYD throttle.
|
|
* @author F. Cañada
|
|
* @date 2025-2026
|
|
* @copyright https://usuaris.tinet.cat/fmco/
|
|
*
|
|
* This file contains functions for initializing and managing the graphical user interface,
|
|
* including timers, drawing routines, and event handling for the PacoMouseCYD throttle.
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// API Documentation
|
|
////////////////////////////////////////////////////////////
|
|
/**
|
|
* @brief Initializes the GUI, timers, and object stacks.
|
|
*/
|
|
void initGUI();
|
|
|
|
/**
|
|
* @brief Clears all system timers.
|
|
*/
|
|
void clearTimers();
|
|
|
|
/**
|
|
* @brief Sets a timer with the specified parameters.
|
|
* @param id Timer ID.
|
|
* @param count Timer count value.
|
|
* @param type Timer type (oneshot, periodic, etc).
|
|
*/
|
|
void setTimer(uint16_t id, uint16_t count, uint16_t type);
|
|
|
|
/**
|
|
* @brief Stops a timer by ID.
|
|
* @param id Timer ID.
|
|
*/
|
|
void stopTimer(uint16_t id);
|
|
|
|
/**
|
|
* @brief Processes all timers, triggering events as needed.
|
|
*/
|
|
void timerProcess();
|
|
|
|
// Add further function documentation here as needed for each public function.
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// End API Documentation
|
|
////////////////////////////////////////////////////////////
|
|
|
|
/* PacoMouseCYD throttle -- F. Cañada 2025-2026 -- https://usuaris.tinet.cat/fmco/
|
|
Very basic Graphical User Interface (GUI) for PacoMouseCYD
|
|
All data in absolute coordinates
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** GUI (Graphical User Interface) *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void initGUI() {
|
|
clearTimers();
|
|
clearEventStack();
|
|
clearObjectStack();
|
|
createObject(OBJ_WIN, WIN_DEFAULT);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** TIMER *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void clearTimers() {
|
|
uint16_t n;
|
|
for (n = 0; n < MAX_SYS_TIMER; n++)
|
|
setTimer(n, 0, TMR_STOP);
|
|
timerSys = millis();
|
|
}
|
|
|
|
|
|
void setTimer(uint16_t id, uint16_t count, uint16_t type) {
|
|
wTimerStack[id].tmrDelay = count;
|
|
wTimerStack[id].tmrCount = count;
|
|
wTimerStack[id].type = type;
|
|
}
|
|
|
|
void stopTimer(uint16_t id) {
|
|
setTimer(id, 0, TMR_STOP);
|
|
}
|
|
|
|
|
|
void timerProcess() {
|
|
uint16_t n;
|
|
if (millis() - timerSys > TMR_RESOLUTION) { // Every 100ms
|
|
timerSys = millis();
|
|
for (n = 0; n < MAX_SYS_TIMER; n++) {
|
|
if (wTimerStack[n].type != TMR_STOP) { // only running timers
|
|
if (wTimerStack[n].tmrCount > 0) {
|
|
wTimerStack[n].tmrCount--;
|
|
}
|
|
else {
|
|
newEvent(OBJ_TIMER, n, EVNT_TIMER); // timer expired
|
|
if (wTimerStack[n].type == TMR_ONESHOT)
|
|
wTimerStack[n].type = TMR_STOP;
|
|
if (wTimerStack[n].type == TMR_PERIODIC)
|
|
wTimerStack[n].tmrCount = wTimerStack[n].tmrDelay;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** EVENT STACK *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void clearEventStack() { // clear pending events
|
|
eventIn = 0;
|
|
eventOut = 0;
|
|
eventsPending = 0;
|
|
}
|
|
|
|
wEvent getEvent() { // get event from stack
|
|
eventOut = (eventOut + 1) & 0x001F;
|
|
eventsPending--;
|
|
return eventStack[eventOut];
|
|
}
|
|
|
|
void putEvent(wEvent event) { // put event on stack
|
|
eventIn = (eventIn + 1) & 0x001F;
|
|
eventsPending++;
|
|
eventStack[eventIn] = event;
|
|
}
|
|
|
|
|
|
void newEvent (uint16_t objType, uint16_t objID, uint16_t eventID) { // create new event
|
|
wEvent event;
|
|
event.objID = objID;
|
|
event.objType = objType;
|
|
event.eventID = eventID;
|
|
putEvent(event);
|
|
}
|
|
|
|
|
|
void sendClickEvent(uint16_t x, uint16_t y) {
|
|
uint16_t pos, type, id, w, h;
|
|
uint16_t xm, xM, ym, yM;
|
|
pos = endObjStack;
|
|
while (pos > 0) {
|
|
id = objStack[pos].objID;
|
|
switch (objStack[pos].objType) {
|
|
case OBJ_WIN:
|
|
xm = winData[id].x;
|
|
ym = winData[id].y;
|
|
xM = xm + winData[id].w;
|
|
yM = ym + winData[id].h;
|
|
break;
|
|
case OBJ_ICON:
|
|
xm = iconData[id].x;
|
|
ym = iconData[id].y;
|
|
xM = xm + iconData[id].w;
|
|
yM = ym + iconData[id].h;
|
|
//DEBUG_MSG("Click Icon ID: %d Pos: %d",objStack[pos].objID,pos)
|
|
break;
|
|
case OBJ_BUTTON:
|
|
xm = buttonData[id].x;
|
|
ym = buttonData[id].y;
|
|
xM = xm + buttonData[id].w;
|
|
yM = ym + buttonData[id].h;
|
|
break;
|
|
case OBJ_BAR:
|
|
if (barData[id].w > barData[id].h) { // Horizontal bar
|
|
if (barData[id].r > (barData[id].h / 2)) { // big cursor
|
|
w = barData[id].r * 2;
|
|
h = barData[id].h / 2;
|
|
xm = barData[id].x - barData[id].r;
|
|
ym = barData[id].y + h - barData[id].r;
|
|
xM = xm + barData[id].w + w + 1;
|
|
yM = ym + w + 1;
|
|
}
|
|
else {
|
|
xm = barData[id].x;
|
|
ym = barData[id].y;
|
|
xM = xm + barData[id].w;
|
|
yM = ym + barData[id].h;
|
|
}
|
|
}
|
|
else { // Vertical bar
|
|
if (barData[id].r > (barData[id].w / 2)) { // big cursor
|
|
h = barData[id].r * 2;
|
|
w = barData[id].w / 2;
|
|
xm = barData[id].x + w - barData[id].r;
|
|
ym = barData[id].y - barData[id].r;
|
|
xM = xm + h + 1;
|
|
yM = ym + barData[id].h + h + 1;
|
|
}
|
|
else {
|
|
xm = barData[id].x;
|
|
ym = barData[id].y;
|
|
xM = xm + barData[id].w;
|
|
yM = ym + barData[id].h;
|
|
}
|
|
}
|
|
break;
|
|
case OBJ_TXT:
|
|
xm = txtData[id].x;
|
|
ym = txtData[id].y;
|
|
xM = xm + txtData[id].w;
|
|
yM = ym + txtData[id].h;
|
|
break;
|
|
case OBJ_SWITCH:
|
|
xm = switchData[id].x;
|
|
ym = switchData[id].y;
|
|
xM = xm + ((switchData[id].h) * 2);
|
|
yM = ym + switchData[id].h;
|
|
break;
|
|
case OBJ_RADIO:
|
|
xm = radioData[id].x;
|
|
ym = radioData[id].y;
|
|
xM = xm + radioData[id].h;
|
|
yM = ym + (radioData[id].h * radioData[id].num);
|
|
break;
|
|
case OBJ_LPIC:
|
|
xm = lpicData[id].x;
|
|
ym = lpicData[id].y;
|
|
xM = xm + LPIC_WIDTH;
|
|
yM = ym + LPIC_HEIGHT;
|
|
break;
|
|
case OBJ_FNC:
|
|
xm = fncData[id].x;
|
|
ym = fncData[id].y;
|
|
xM = xm + FNC_WIDTH;
|
|
yM = ym + FNC_HEIGHT;
|
|
break;
|
|
case OBJ_KEYBOARD:
|
|
xm = keybData[id].x;
|
|
ym = keybData[id].y;
|
|
switch (keybData[id].type) {
|
|
case KEYB_KEYPAD:
|
|
xM = xm + KEYPAD_WIDTH;
|
|
yM = ym + KEYPAD_HEIGHT;
|
|
break;
|
|
case KEYB_KEYPAD_BIG:
|
|
case KEYB_KEYPAD_OPT:
|
|
xM = xm + KEYPAD_BIG_WIDTH;
|
|
yM = ym + KEYPAD_BIG_HEIGHT;
|
|
break;
|
|
default:
|
|
xM = xm + KEYB_WIDTH;
|
|
yM = ym + KEYB_HEIGHT;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
xm = tft.width(); // other objects don't generate click event
|
|
ym = tft.height();
|
|
xM = xm;
|
|
yM = ym;
|
|
}
|
|
//if ((x >= xm) && (x < xM) && (y >= ym) && (y < yM)) { // check if click is inside object
|
|
if (isInside(x, y, xm, xM, ym, yM)) {
|
|
newEvent(objStack[pos].objType, id, EVNT_CLICK);
|
|
lastClickX = x;
|
|
lastClickY = y;
|
|
return;
|
|
}
|
|
if ((pos == lastWinStack) && isModalWindow(objStack[lastWinStack].objID)) // With Modal window only cliks inside last window are valid
|
|
return;
|
|
pos--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** GRAPHIC OBJECT STACK *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void clearObjectStack() { // Clear list of objects
|
|
endObjStack = 0;
|
|
objStack[endObjStack].objType = OBJ_UNDEF;
|
|
lastWinStack = endObjStack;
|
|
}
|
|
|
|
|
|
uint16_t createObject (uint16_t obj, uint16_t id) { // Add a new object to the list of objects
|
|
objStack[endObjStack].objType = obj;
|
|
objStack[endObjStack].objID = id;
|
|
if (obj == OBJ_WIN)
|
|
lastWinStack = endObjStack;
|
|
endObjStack++;
|
|
objStack[endObjStack].objType = OBJ_UNDEF;
|
|
//DEBUG_MSG("Create Object. Pos: %d", endObjStack - 1)
|
|
return (endObjStack - 1);
|
|
}
|
|
|
|
|
|
uint16_t findWindow(uint16_t id) {
|
|
uint16_t pos, cnt;
|
|
pos = OBJ_NOT_FOUND;
|
|
cnt = endObjStack;
|
|
while (cnt > 0) {
|
|
if ((objStack[cnt].objType == OBJ_WIN) && (objStack[cnt].objID == id)) {
|
|
pos = cnt;
|
|
cnt = 1; // finded, don't search anymore
|
|
}
|
|
cnt--;
|
|
}
|
|
DEBUG_MSG("Window finded at pos: %d", pos)
|
|
return pos;
|
|
}
|
|
|
|
uint16_t findPreviousWindow(uint16_t cnt) { // find previous window from position
|
|
uint16_t pos;
|
|
pos = 0;
|
|
while (cnt > 0) {
|
|
if (objStack[cnt].objType == OBJ_WIN) {
|
|
pos = cnt;
|
|
cnt = 1; // finded, don't search anymore
|
|
}
|
|
cnt--;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
uint16_t findNextWindow (uint16_t cnt) { // find next window from position
|
|
//uint16_t pos;
|
|
DEBUG_MSG("Curr. window: %d, End stack: %d", cnt, endObjStack)
|
|
cnt++;
|
|
while (cnt < endObjStack) {
|
|
if (objStack[cnt].objType == OBJ_WIN) {
|
|
//pos = cnt;
|
|
return cnt;
|
|
}
|
|
cnt++;
|
|
}
|
|
return endObjStack;
|
|
}
|
|
|
|
|
|
void closeWindow(uint16_t id) { // Close window
|
|
uint16_t pos, nxt, cnt, posRedraw;
|
|
if (objStack[lastWinStack].objID == id) { // check if it is last window
|
|
endObjStack = lastWinStack;
|
|
objStack[endObjStack].objType = OBJ_UNDEF;
|
|
DEBUG_MSG("Closing last window... %d", id)
|
|
}
|
|
else {
|
|
DEBUG_MSG("Closing other window ID: %d", id)
|
|
pos = findWindow(id); // find window and next window
|
|
if (pos == OBJ_NOT_FOUND)
|
|
return;
|
|
nxt = findNextWindow(pos);
|
|
DEBUG_MSG("Pos: %d, Next window: %d", pos, nxt)
|
|
cnt = 0;
|
|
while ((cnt + nxt) < endObjStack + 1) { // move rest of objects
|
|
objStack[pos + cnt] = objStack[nxt + cnt];
|
|
cnt++;
|
|
}
|
|
endObjStack = pos + cnt - 1;
|
|
DEBUG_MSG("Closing window pos: %d, nxt: %d", pos, nxt)
|
|
}
|
|
printObjStack();
|
|
//lastWinStack = findPreviousWindow(endObjStack); // update last window pointer
|
|
for (pos = findContainerWindow(id); pos < endObjStack; pos++) {
|
|
if (objStack[pos].objType == OBJ_WIN) {
|
|
newEvent (OBJ_WIN, objStack[pos].objID, EVNT_DRAW); // re-draw other windows
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
uint16_t findContainerWindow (uint16_t id) { // Find window of stack that contains the closed window
|
|
uint16_t pos, x1, y1, xm, xM, ym, yM, winID;
|
|
lastWinStack = findPreviousWindow(endObjStack); // update last window pointer
|
|
pos = lastWinStack;
|
|
while (pos) {
|
|
winID = objStack[pos].objID;
|
|
xm = winData[winID].x;
|
|
xM = xm + winData[winID].w + 1;
|
|
ym = winData[winID].y;
|
|
yM = xm + winData[winID].h + 1;
|
|
x1 = winData[id].x;
|
|
y1 = winData[id].y;
|
|
if (isInside(x1, y1, xm, xM, ym, yM)) { // Left top corner
|
|
if (isInside(x1 + winData[id].w, y1, xm, xM, ym, yM)) { // Rigth top corner
|
|
if (isInside(x1, y1 + winData[id].h, xm, xM, ym, yM)) { // Left bottom corner
|
|
if (isInside(x1 + winData[id].w, y1 + winData[id].h, xm, xM, ym, yM)) { // Right bottom corner
|
|
DEBUG_MSG("Container window at %d", pos);
|
|
return pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pos = findPreviousWindow(pos - 1);
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
void setModalWindow(uint16_t id, bool state) {
|
|
winData[id].modal = state;
|
|
}
|
|
|
|
|
|
bool isModalWindow (uint16_t id) {
|
|
return winData[id].modal;
|
|
}
|
|
|
|
|
|
bool isWindow(uint16_t win) {
|
|
return (objStack[lastWinStack].objID == win);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** DRAW OBJECTS *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void printLabelTxt(uint16_t id) {
|
|
char label[MAX_LABEL_LNG];
|
|
uint16_t cnt, yPos;
|
|
char chr;
|
|
const char *language;
|
|
language = translations[id][currLanguage];
|
|
if (language == NULL)
|
|
language = translations[id][LANG_ENGLISH];
|
|
tft.setFreeFont(labelData[id].font);
|
|
tft.setTextColor(labelData[id].color);
|
|
tft.setTextDatum(labelData[id].align);
|
|
yPos = labelData[id].y;
|
|
cnt = 0;
|
|
chr = *language++;
|
|
while (chr) {
|
|
if (chr == '\n') {
|
|
label[cnt] = 0;
|
|
tft.drawString(label, labelData[id].x, yPos, GFXFF);
|
|
yPos += tft.fontHeight();
|
|
cnt = 0;
|
|
chr = *language++;
|
|
}
|
|
else {
|
|
label[cnt++] = chr;
|
|
chr = *language++;
|
|
}
|
|
}
|
|
label[cnt] = 0;
|
|
tft.drawString(label, labelData[id].x, yPos, GFXFF);
|
|
}
|
|
|
|
|
|
const char *getParmNum(const char *str, uint16_t *parm) {
|
|
char chr;
|
|
uint16_t value;
|
|
value = 0;
|
|
chr = *str;
|
|
while (isDigit(chr)) {
|
|
value = (value * 10) + (chr - '0');
|
|
str++;
|
|
chr = *str;
|
|
}
|
|
*parm = value;
|
|
return str;
|
|
}
|
|
|
|
|
|
void drawDrawStr(uint16_t id) {
|
|
char chr, cmd;
|
|
uint16_t x, y, w, c, x2, y2, color, value, depth, band, colorRGB;
|
|
const char *str;
|
|
bool isSprite;
|
|
x = drawStrData[id].x;
|
|
y = drawStrData[id].y;
|
|
color = COLOR_TRANSPARENT;
|
|
depth = 8;
|
|
band = 15;
|
|
str = drawStrData[id].str;
|
|
isSprite = false;
|
|
chr = *str++;
|
|
while (chr != 0) {
|
|
if (chr != ',')
|
|
cmd = chr;
|
|
switch (chr) {
|
|
case 'K': // Kn: Color n
|
|
str = getParmNum(str, &color);
|
|
//DEBUG_MSG("Color: %d", color);
|
|
break;
|
|
case 'k': // kn: Sprite color depth
|
|
str = getParmNum(str, &depth);
|
|
sprite.setColorDepth(depth);
|
|
break;
|
|
case ',':
|
|
str = getParmNum(str, &value);
|
|
//DEBUG_MSG("Value: %d", value);
|
|
switch (cmd) {
|
|
case 'R':
|
|
if (isSprite)
|
|
sprite.fillRect(x, y, w, value, colorDraw[color]);
|
|
else
|
|
tft.fillRect(x, y, w, value, colorDraw[color]);
|
|
break;
|
|
case 'r':
|
|
if (isSprite)
|
|
sprite.drawRect(x, y, w, value, colorDraw[color]);
|
|
else
|
|
tft.drawRect(x, y, w, value, colorDraw[color]);
|
|
break;
|
|
case 'L':
|
|
if (isSprite)
|
|
sprite.drawLine(x, y, w, value, colorDraw[color]);
|
|
else
|
|
tft.drawLine(x, y, w, value, colorDraw[color]);
|
|
break;
|
|
case 'l':
|
|
if (isSprite)
|
|
sprite.drawLine(x, y, w, value, colorDraw[color]);
|
|
else
|
|
tft.drawLine(x, y, w, value, colorDraw[color]);
|
|
x = w;
|
|
y = value;
|
|
break;
|
|
case 'p':
|
|
y2 = value;
|
|
break;
|
|
case 'T':
|
|
if (isSprite)
|
|
sprite.fillTriangle(x, y, x2, y2, w, value, colorDraw[color]);
|
|
else
|
|
tft.fillTriangle(x, y, x2, y2, w, value, colorDraw[color]);
|
|
break;
|
|
case 't':
|
|
if (isSprite)
|
|
sprite.drawTriangle(x, y, x2, y2, w, value, colorDraw[color]);
|
|
else
|
|
tft.drawTriangle(x, y, x2, y2, w, value, colorDraw[color]);
|
|
break;
|
|
case 'S':
|
|
sprite.createSprite(w, value); // then create the sprite
|
|
sprite.fillSprite(colorDraw[color]);
|
|
isSprite = true;
|
|
x = 0;
|
|
y = 0;
|
|
break;
|
|
case 'D':
|
|
colorRGB = colorDraw[color];
|
|
for (c = 0; c < value; c += band) {
|
|
if (isSprite)
|
|
sprite.fillRect(x, y + c, w, band, colorRGB);
|
|
else
|
|
tft.fillRect(x, y + c, w, band, colorRGB);
|
|
if (colorRGB & 0x001F)
|
|
colorRGB -= 0x0001;
|
|
if (colorRGB & 0x07E0)
|
|
colorRGB -= 0x0040;
|
|
if (colorRGB & 0xF800)
|
|
colorRGB -= 0x0800;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 'R': // Rw,h: fill Rectangle
|
|
case 'r': // rw,h: draw Rectangle
|
|
case 'D': // Dw,h: Degrade rectangle
|
|
str = getParmNum(str, &w);
|
|
//DEBUG_MSG("Rect W: %d", w);
|
|
break;
|
|
case 'C': // Cr: fill Circle radius r
|
|
str = getParmNum(str, &c);
|
|
if (isSprite)
|
|
sprite.fillCircle(x, y, c, colorDraw[color]);
|
|
else
|
|
tft.fillCircle(x, y, c, colorDraw[color]);
|
|
break;
|
|
case 'c': // cr : draw Circle radius r
|
|
str = getParmNum(str, &c);
|
|
if (isSprite)
|
|
sprite.drawCircle(x, y, c, colorDraw[color]);
|
|
else
|
|
tft.drawCircle(x, y, c, colorDraw[color]);
|
|
break;
|
|
case 'l': // lx,y: draw polyline to x,y (X,Y set to end position: x,y)
|
|
case 'L': // Lx,y: draw Line to x,y
|
|
str = getParmNum(str, &w);
|
|
break;
|
|
case 't': // tx,y: draw triangle (X,Y),(px,y),(tx,y)
|
|
case 'T': // Tx,y: fill triangle (X,Y),(px,y),(Tx,y)
|
|
str = getParmNum(str, &w);
|
|
break;
|
|
case 'S': // Sw,h: create sprite and draw string into it. x & y set to 0, background color defined with Kn -> KnkdSw,h
|
|
str = getParmNum(str, &w);
|
|
break;
|
|
case 's': // sn: draw sprite with transparent color n
|
|
str = getParmNum(str, &value);
|
|
//sprite.pushSprite(drawStrData[id].x, drawStrData[id].y, value);
|
|
sprite.pushSprite(drawStrData[id].x, drawStrData[id].y, colorDraw[value]);
|
|
sprite.deleteSprite();
|
|
isSprite = false;
|
|
break;
|
|
case 'X': // X: x position
|
|
str = getParmNum(str, &x);
|
|
//DEBUG_MSG("X: %d", x);
|
|
break;
|
|
case 'x': // x: increment x position
|
|
str = getParmNum(str, &value);
|
|
x += value;
|
|
break;
|
|
case 'Y': // Y: y position
|
|
str = getParmNum(str, &y);
|
|
//DEBUG_MSG("Y: %d", y);
|
|
break;
|
|
case 'y': // y: increment y position
|
|
str = getParmNum(str, &value);
|
|
y += value;
|
|
break;
|
|
case 'p':
|
|
str = getParmNum(str, &x2); // p: second point (for triangles) -> XxYypx,yTx,y
|
|
break;
|
|
case 'd': // dn: Degrade band height
|
|
str = getParmNum(str, &band);
|
|
break;
|
|
}
|
|
chr = *str++;
|
|
}
|
|
}
|
|
|
|
|
|
void drawObject (uint16_t type, uint16_t id) {
|
|
uint16_t value, state, w, h;
|
|
char buf[20];
|
|
delay(0);
|
|
switch (type) {
|
|
case OBJ_WIN:
|
|
tft.fillRect(winData[id].x, winData[id].y, winData[id].w, winData[id].h, winData[id].backgnd); // window box
|
|
tft.drawRect(winData[id].x, winData[id].y, winData[id].w, winData[id].h, winData[id].color);
|
|
break;
|
|
case OBJ_LABEL:
|
|
printLabelTxt(id);
|
|
break;
|
|
case OBJ_CHAR:
|
|
tft.setFreeFont(charData[id].font);
|
|
tft.setTextColor(charData[id].color);
|
|
tft.setTextDatum(MC_DATUM);
|
|
tft.drawChar(charData[id].chr, charData[id].x, charData[id].y);
|
|
break;
|
|
case OBJ_TXT:
|
|
tft.fillRoundRect(txtData[id].x + 1, txtData[id].y + 1, txtData[id].w - 2, txtData[id].h - 2, 3, txtData[id].backgnd);
|
|
tft.drawRoundRect(txtData[id].x, txtData[id].y, txtData[id].w, txtData[id].h, 3, txtData[id].border);
|
|
tft.setFreeFont(txtData[id].font);
|
|
tft.setTextColor(txtData[id].color);
|
|
if (txtData[id].alignCenter) {
|
|
tft.setTextDatum(MC_DATUM);
|
|
tft.drawString(txtData[id].buf, txtData[id].x + (txtData[id].w / 2), txtData[id].y + (txtData[id].h / 2), GFXFF);
|
|
}
|
|
else {
|
|
tft.setTextDatum(ML_DATUM);
|
|
tft.drawString(txtData[id].buf, txtData[id].x + 3, txtData[id].y + (txtData[id].h / 2), GFXFF);
|
|
}
|
|
break;
|
|
case OBJ_DRAWSTR:
|
|
drawDrawStr(id);
|
|
break;
|
|
case OBJ_ICON:
|
|
//tft.pushImage(260, 120, 24, 32, (uint16_t*)cara_paco24x32, false); // 16 bits RGB565
|
|
tft.drawBitmap(iconData[id].x, iconData[id].y, iconData[id].bitmap, iconData[id].w, iconData[id].h, iconData[id].color); // monochrome
|
|
//DEBUG_MSG("Print Icon ID: %d, X:%d, Y:%d, PTR:%d, W:%d, H:%d, Color:%d",id, iconData[id].x, iconData[id].y, iconData[id].bitmap, iconData[id].w, iconData[id].h, iconData[id].color);
|
|
break;
|
|
case OBJ_BUTTON:
|
|
tft.fillRoundRect(buttonData[id].x + 1, buttonData[id].y + 1, buttonData[id].w - 2, buttonData[id].h - 2, 3, buttonData[id].backgnd);
|
|
tft.drawRoundRect(buttonData[id].x, buttonData[id].y, buttonData[id].w, buttonData[id].h, 4, buttonData[id].border);
|
|
if (buttonData[id].objType != OBJ_UNDEF)
|
|
drawObject(buttonData[id].objType, buttonData[id].objID);
|
|
break;
|
|
case OBJ_BAR:
|
|
if (barData[id].w > barData[id].h) { // Horizontal bar
|
|
if (barData[id].r > (barData[id].h / 2)) {
|
|
w = barData[id].r * 2;
|
|
h = barData[id].h / 2;
|
|
tft.fillRect(barData[id].x - barData[id].r, barData[id].y + h - barData[id].r, barData[id].w + w + 1, w + 1, barData[id].backgnd);
|
|
}
|
|
value = map (barData[id].value, barData[id].min, barData[id].max, 0, barData[id].w);
|
|
tft.fillRect(barData[id].x + 1, barData[id].y + 1, value, barData[id].h - 2, barData[id].colorOn);
|
|
tft.fillRect(barData[id].x + value + 1, barData[id].y + 1, barData[id].w - value - 2, barData[id].h - 2, barData[id].colorOff);
|
|
tft.drawRect(barData[id].x, barData[id].y, barData[id].w, barData[id].h, barData[id].border);
|
|
if (barData[id].r > 0) {
|
|
tft.fillCircle(barData[id].x + value, barData[id].y + (barData[id].h / 2), barData[id].r, barData[id].border);
|
|
}
|
|
}
|
|
else { // Vertical bar
|
|
if (barData[id].r > (barData[id].w / 2)) {
|
|
h = barData[id].r * 2;
|
|
w = barData[id].w / 2;
|
|
tft.fillRect(barData[id].x + w - barData[id].r, barData[id].y - barData[id].r, h + 1, barData[id].h + h + 1, barData[id].backgnd);
|
|
}
|
|
value = map (barData[id].value, barData[id].min, barData[id].max, 0, barData[id].h);
|
|
tft.fillRect(barData[id].x + 1, barData[id].y + barData[id].h - value - 1, barData[id].w - 2, value, barData[id].colorOn);
|
|
tft.fillRect(barData[id].x + 1, barData[id].y + 1, barData[id].w - 2, barData[id].h - value, barData[id].colorOff);
|
|
tft.drawRect(barData[id].x, barData[id].y, barData[id].w, barData[id].h, barData[id].border);
|
|
if (barData[id].r > 0) {
|
|
tft.fillCircle(barData[id].x + w, barData[id].y + barData[id].h - value, barData[id].r, barData[id].border);
|
|
}
|
|
}
|
|
break;
|
|
case OBJ_SWITCH:
|
|
value = switchData[id].state ? switchData[id].colorOn : switchData[id].colorOff;
|
|
tft.fillRoundRect(switchData[id].x, switchData[id].y, switchData[id].h * 2, switchData[id].h, switchData[id].h / 2, value);
|
|
value = switchData[id].h / 2;
|
|
state = switchData[id].state ? value + switchData[id].h : value;
|
|
tft.fillCircle(switchData[id].x + state, switchData[id].y + value, value - 2, switchData[id].colorKnob);
|
|
break;
|
|
case OBJ_RADIO:
|
|
w = radioData[id].h / 2;
|
|
h = 0;
|
|
for (value = 0; value < radioData[id].num; value++) {
|
|
tft.fillCircle(radioData[id].x + w, radioData[id].y + w + h, radioData[id].r, radioData[id].backgnd);
|
|
tft.drawCircle(radioData[id].x + w, radioData[id].y + w + h, radioData[id].r, radioData[id].border);
|
|
if (value == radioData[id].value)
|
|
tft.fillCircle(radioData[id].x + w, radioData[id].y + w + h, radioData[id].r - 2, radioData[id].border);
|
|
h += radioData[id].h;
|
|
}
|
|
break;
|
|
case OBJ_FNC:
|
|
value = fncData[id].num;
|
|
tft.drawBitmap(fncData[id].x, fncData[id].y, full32, 32, 32, fncData[id].backgnd); // delete
|
|
tft.drawBitmap(fncData[id].x, fncData[id].y, funcIcon[fncData[id].idIcon], 32, 32, fncData[id].color); // black
|
|
if (fncData[id].state)
|
|
tft.drawBitmap(fncData[id].x, fncData[id].y, funcIcon[fncData[id].idIcon + 1], 32, 32, fncData[id].colorOn); // yellow
|
|
if (value <= FNC_MAX) {
|
|
snprintf(buf, 4, "%d", value);
|
|
tft.setFreeFont(FSSB6);
|
|
tft.setTextColor(COLOR_WHITE); // white
|
|
tft.setTextDatum(BR_DATUM);
|
|
tft.drawString(buf, fncData[id].x + 31, fncData[id].y + 31, GFXFF);
|
|
}
|
|
break;
|
|
|
|
case OBJ_LPIC:
|
|
if (lpicData[id].id > 999) {
|
|
sprintf (FileName, "/image/%d.bmp", lpicData[id].id );
|
|
if (! drawBmp (FileName, lpicData[id].x, lpicData[id].y)) {
|
|
value = sdDetected ? SYS_NO_LOK : SYS_ELOK;
|
|
tft.drawBitmap(lpicData[id].x, lpicData[id].y, sysLocoPic[value], LPIC_WIDTH, LPIC_HEIGHT, COLOR_BLACK); // monochrome
|
|
}
|
|
}
|
|
else {
|
|
tft.drawBitmap(lpicData[id].x, lpicData[id].y, sysLocoPic[lpicData[id].id], LPIC_WIDTH, LPIC_HEIGHT, COLOR_BLACK); // monochrome
|
|
}
|
|
break;
|
|
case OBJ_GAUGE:
|
|
if (gaugeData[id].r > 0) { // Normal gauge
|
|
tft.drawArc(gaugeData[id].x, gaugeData[id].y, gaugeData[id].r, gaugeData[id].r - 15, 52, 52 + gaugeData[id].value, gaugeData[id].color, gaugeData[id].backgnd, false);
|
|
tft.drawArc(gaugeData[id].x, gaugeData[id].y, gaugeData[id].r, gaugeData[id].r - 15, 52 + gaugeData[id].value, 308, gaugeData[id].backgnd, gaugeData[id].color, false);
|
|
}
|
|
else { // Speed gauge
|
|
tft.setPivot(gaugeData[id].x, gaugeData[id].y); // Set pivot to middle of TFT screen
|
|
sprite.setColorDepth(8); // Create an 8bpp Sprite
|
|
sprite.createSprite(11, 13); // 8bpp requires 11 * 13 = 143 bytes
|
|
sprite.setPivot(70, 7); // Set pivot relative to top left corner of Sprite
|
|
sprite.fillSprite(gaugeData[id].backgnd); // Fill the Sprite with background
|
|
sprite.drawBitmap(0, 0, arc10, 11, 13, gaugeData[id].color);
|
|
for (value = 330; value < 360; value += 10) { // draw dial
|
|
sprite.pushRotated(value);
|
|
}
|
|
for (value = 0; value < 230; value += 10) {
|
|
sprite.pushRotated(value);
|
|
}
|
|
sprite.deleteSprite();
|
|
delay(0);
|
|
tft.drawArc(gaugeData[id].x, gaugeData[id].y, 71, 69, 270, 315, COLOR_RED, gaugeData[id].backgnd, false);
|
|
tft.drawCircle(gaugeData[id].x, gaugeData[id].y, 75, COLOR_GOLD);
|
|
tft.fillCircle(gaugeData[id].x, gaugeData[id].y, 20, gaugeData[id].color);
|
|
delay(0);
|
|
updateSpeedDir(); // draw needle & dir
|
|
delay(0);
|
|
}
|
|
break;
|
|
case OBJ_KEYBOARD:
|
|
switch (keybData[id].type) {
|
|
case KEYB_ALPHA:
|
|
drawKeyboard(keybData[id].x, keybData[id].y, KeybAlpha);
|
|
break;
|
|
case KEYB_NUM:
|
|
drawKeyboard(keybData[id].x, keybData[id].y, KeybNum);
|
|
break;
|
|
case KEYB_SYM:
|
|
drawKeyboard(keybData[id].x, keybData[id].y, KeybSym);
|
|
break;
|
|
case KEYB_KEYPAD_BIG:
|
|
drawKeypadBig(keybData[id].x, keybData[id].y, KeybKeypad, true);
|
|
break;
|
|
case KEYB_KEYPAD_OPT:
|
|
drawKeypadBig(keybData[id].x, keybData[id].y, KeybKeypad, false);
|
|
break;
|
|
case KEYB_KEYPAD:
|
|
drawKeypad(keybData[id].x, keybData[id].y, KeybKeypad);
|
|
break;
|
|
default:
|
|
drawKeyboard(keybData[id].x, keybData[id].y, KeybAlphaCaps);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
|
|
}
|
|
delay(0);
|
|
}
|
|
|
|
|
|
void drawWindow(uint16_t id) {
|
|
uint16_t pos;
|
|
//wEvent redraw;
|
|
DEBUG_MSG("Finding window for printing... %d", id)
|
|
pos = findWindow(id);
|
|
if (pos != OBJ_NOT_FOUND) {
|
|
DEBUG_MSG("Printing window box ID: %d", id)
|
|
drawObject(OBJ_WIN, id); // draw window box
|
|
pos++;
|
|
printObjStack();
|
|
while ((objStack[pos].objType != OBJ_UNDEF) && (objStack[pos].objType != OBJ_WIN)) { // draw objects in window
|
|
drawObject(objStack[pos].objType, objStack[pos].objID);
|
|
//DEBUG_MSG("Drawing object pos: %d", pos)
|
|
pos++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool drawBmp(const char *filename, int16_t x, int16_t y) { // Draw BMP file (24 bits)
|
|
File bmpFS;
|
|
if ((x >= tft.width()) || (y >= tft.height()))
|
|
return false;
|
|
bmpFS = SD.open(filename); // Open requested file on SD card
|
|
if (!bmpFS) {
|
|
DEBUG_MSG("File %s not found", filename );
|
|
return false;
|
|
}
|
|
uint32_t seekOffset;
|
|
uint16_t w, h, row, col;
|
|
uint8_t r, g, b;
|
|
|
|
uint32_t startTime = millis();
|
|
if (read16(bmpFS) == 0x4D42) { // BMP signature
|
|
read32(bmpFS); // File size
|
|
read32(bmpFS); // Read & ignore creator bytes
|
|
seekOffset = read32(bmpFS); // Start of image data
|
|
read32(bmpFS);
|
|
w = read32(bmpFS);
|
|
h = read32(bmpFS);
|
|
|
|
if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0)) { // # planes -- must be '1', 24 bits per pixel, 0 = uncompressed
|
|
y += h - 1;
|
|
|
|
tft.setSwapBytes(true);
|
|
bmpFS.seek(seekOffset);
|
|
|
|
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
|
|
uint8_t lineBuffer[w * 3];
|
|
|
|
for (row = 0; row < h; row++) {
|
|
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
|
uint8_t* bptr = lineBuffer;
|
|
uint16_t* tptr = (uint16_t*)lineBuffer;
|
|
// Convert 24 to 16 bit colours
|
|
for (uint16_t col = 0; col < w; col++) { // Convert pixel from BMP to TFT format (RGB565)
|
|
b = *bptr++;
|
|
g = *bptr++;
|
|
r = *bptr++;
|
|
*tptr++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
|
}
|
|
if (padding) // Read any line padding
|
|
bmpFS.read((uint8_t*)tptr, padding);
|
|
tft.pushImage(x, y--, w, 1, (uint16_t*)lineBuffer); // Push the pixel row to screen, pushImage will crop the line if needed
|
|
delay(0);
|
|
}
|
|
DEBUG_MSG("%s Loaded in %lu ms", filename, (millis() - startTime))
|
|
}
|
|
else {
|
|
DEBUG_MSG("BMP format not recognized.")
|
|
}
|
|
}
|
|
bmpFS.close();
|
|
tft.setSwapBytes(false);
|
|
return true;
|
|
}
|
|
|
|
|
|
uint16_t read16(fs::File & f) { // helper functions for BMP load
|
|
uint16_t result;
|
|
((uint8_t *)&result)[0] = f.read(); // LSB
|
|
((uint8_t *)&result)[1] = f.read(); // MSB
|
|
return result;
|
|
}
|
|
|
|
|
|
uint32_t read32(fs::File & f) {
|
|
uint32_t result;
|
|
((uint8_t *)&result)[0] = f.read(); // LSB
|
|
((uint8_t *)&result)[1] = f.read();
|
|
((uint8_t *)&result)[2] = f.read();
|
|
((uint8_t *)&result)[3] = f.read(); // MSB
|
|
return result;
|
|
}
|
|
|
|
|
|
void drawKey (uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
|
tft.fillRoundRect(x + 1, y + 1, w - 2, h - 2, 3, COLOR_NAVY); //inner button color
|
|
tft.drawRoundRect(x, y, w, h, 3, COLOR_WHITE); // outter button color
|
|
}
|
|
|
|
|
|
void drawKeyboard (uint16_t posX, uint16_t posY, const char type[][12]) {
|
|
int x, y, shiftRight;
|
|
tft.fillRect(posX + 1, posY, KEYB_WIDTH - 2, KEYB_HEIGHT - 2, COLOR_BACKGROUND);
|
|
tft.setFreeFont(FSSB9);
|
|
tft.setTextColor(COLOR_WHITE);
|
|
//tft.setTextDatum(MC_DATUM);
|
|
for (y = 0; y < 3; y++) { // Keys
|
|
shiftRight = type[y][0];
|
|
for (x = 2; x < type[y][1]; x++) {
|
|
drawKey(posX + 8 + shiftRight + (23 * (x - 2)) , posY + (30 * y), 20, 25);
|
|
tft.setCursor(posX + 11 + shiftRight + (23 * (x - 2)) , posY + 5 + 11 + (30 * y));
|
|
tft.print(type[y][x]);
|
|
}
|
|
}
|
|
drawKey(posX + 5, posY + 60, 30, 25); // ShiftKey
|
|
tft.drawBitmap(posX + 11, posY + 65, shift, 16, 16, COLOR_WHITE); // delete
|
|
if (type == KeybAlphaCaps)
|
|
tft.drawFastHLine(posX + 11 + 6, posY + 65 + 12, 5, COLOR_WHITE);
|
|
drawKey(posX + 5, posY + 90, 40, 25); // Num
|
|
tft.setCursor(posX + 8, posY + 11 + 95);
|
|
tft.print("123");
|
|
drawKey(posX + 200, posY + 60, 30, 25); // BackSpace
|
|
tft.drawBitmap(posX + 206, posY + 64, del, 16, 16, COLOR_WHITE); // delete
|
|
drawKey(posX + 189, posY + 90, 45, 25); // Special Characters
|
|
tft.setCursor(posX + 195, posY + 11 + 95);
|
|
tft.print("#?+");
|
|
drawKey(posX + 52, posY + 90, 130, 25); // Spacebar
|
|
}
|
|
|
|
|
|
void drawKeypad (uint16_t posX, uint16_t posY, const char type[][12]) {
|
|
int x, y, shiftRight;
|
|
tft.fillRect(posX + 1, posY + 1, KEYPAD_WIDTH - 2, KEYPAD_HEIGHT - 2, COLOR_BACKGROUND);
|
|
tft.setFreeFont(FSSB9);
|
|
tft.setTextColor(COLOR_WHITE);
|
|
tft.setTextDatum(MC_DATUM);
|
|
for (y = 0; y < 3; y++) {
|
|
shiftRight = type[y][0];
|
|
for (x = 2; x < type[y][1]; x++) {
|
|
drawKey(posX + 8 + shiftRight + (23 * (x - 2)) , posY + (30 * y), 20, 25);
|
|
tft.setCursor(posX + 12 + shiftRight + (23 * (x - 2)) , posY + 5 + 11 + (30 * y));
|
|
tft.print(type[y][x]);
|
|
}
|
|
}
|
|
drawKey(posX + 8, posY + 90, 32, 25); // Num 0
|
|
tft.setCursor(posX + 18, posY + 11 + 95);
|
|
tft.print("0");
|
|
drawKey(posX + 43, posY + 90, 30, 25); // BackSpace
|
|
tft.drawBitmap(posX + 43 + 7, posY + 94, del, 16, 16, COLOR_WHITE); // delete
|
|
}
|
|
|
|
void drawKeypadBig (uint16_t posX, uint16_t posY, const char type[][12], bool enter) {
|
|
int x, y, shiftRight;
|
|
tft.fillRect(posX + 1, posY + 1, KEYPAD_BIG_WIDTH - 2, KEYPAD_BIG_HEIGHT - 2, COLOR_BACKGROUND);
|
|
tft.setFreeFont(FSSB12);
|
|
tft.setTextColor(COLOR_WHITE);
|
|
tft.setTextDatum(MC_DATUM);
|
|
for (y = 0; y < 3; y++) {
|
|
shiftRight = type[y][0];
|
|
for (x = 2; x < type[y][1]; x++) {
|
|
drawKey(posX + 8 + shiftRight + (46 * (x - 2)) , posY + (50 * y), 40, 40);
|
|
tft.setCursor(posX + 20 + shiftRight + (46 * (x - 2)) , posY + 5 + 25 + (50 * y));
|
|
tft.print(type[y][x]);
|
|
}
|
|
}
|
|
drawKey(posX + 8, posY + 150, 40, 40); // BackSpace
|
|
tft.drawBitmap(posX + 20, posY + 160, del, 16, 16, COLOR_WHITE); // delete
|
|
drawKey(posX + 8 + 46, posY + 150, 40, 40); // Num 0
|
|
tft.setCursor(posX + 20 + 46, posY + 25 + 150);
|
|
tft.print("0");
|
|
drawKey(posX + 16 + 84, posY + 150, 40, 40); // Enter
|
|
if (enter)
|
|
tft.drawBitmap(posX + 84 + 26, posY + 160, arrowL, 16, 16, COLOR_GREENYELLOW); // enter
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// ***** SUPPORT *****
|
|
////////////////////////////////////////////////////////////
|
|
|
|
void getLabelTxt(uint16_t id, char *buf) {
|
|
uint16_t cnt;
|
|
char chr;
|
|
const char *language;
|
|
language = translations[id][currLanguage];
|
|
if (language == NULL)
|
|
language = translations[id][LANG_ENGLISH];
|
|
cnt = 0;
|
|
chr = *language++;
|
|
//while (chr != END_OF_TEXT) {
|
|
while (chr) {
|
|
buf[cnt++] = chr;
|
|
chr = *language++;
|
|
}
|
|
buf[cnt] = '\0';;
|
|
}
|
|
|
|
|
|
char getKeyTyped(uint16_t id, uint16_t x, uint16_t y) { // get key typed on keyboard object
|
|
uint16_t x0, y0, xm, ym, xM, yM;
|
|
uint16_t xKey, yKey, xShift, xNum;
|
|
|
|
x0 = keybData[id].x; // window position
|
|
y0 = keybData[id].y;
|
|
switch (keybData[id].type) {
|
|
case KEYB_KEYPAD: // keypad
|
|
ym = y0 + 90; // last row
|
|
yM = ym + 25;
|
|
xm = x0 + 8; // Num 0
|
|
xM = xm + 32;
|
|
if (isInside(x, y, xm, xM, ym, yM))
|
|
return '0';
|
|
xm = x0 + 43; // Backspace
|
|
xM = xm + 32;
|
|
if (isInside(x, y, xm, xM, ym, yM))
|
|
return CHR_BKSPC;
|
|
for (yKey = 0; yKey < 3; yKey++) { // check first three rows
|
|
xShift = KeybKeypad[yKey][0];
|
|
xNum = KeybKeypad[yKey][1];
|
|
for (xKey = 2; xKey < xNum; xKey++) {
|
|
xm = x0 + xShift + 8 + (23 * (xKey - 2));
|
|
xM = xm + 20;
|
|
ym = y0 + (30 * yKey);
|
|
yM = ym + 25;
|
|
if (isInside(x, y, xm, xM, ym, yM)) {
|
|
return KeybKeypad[yKey][xKey];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case KEYB_KEYPAD_BIG: // keypad big
|
|
case KEYB_KEYPAD_OPT:
|
|
ym = y0 + 150; // last row
|
|
yM = ym + 40;
|
|
xm = x0 + 8; // Backspace
|
|
xM = xm + 40;
|
|
if (isInside(x, y, xm, xM, ym, yM))
|
|
return CHR_BKSPC;
|
|
xm = x0 + 8 + 46; // Num 0
|
|
xM = xm + 40;
|
|
if (isInside(x, y, xm, xM, ym, yM))
|
|
return '0';
|
|
xm = x0 + 16 + 84; // Enter
|
|
xM = xm + 40;
|
|
if (isInside(x, y, xm, xM, ym, yM))
|
|
return '\n';
|
|
for (yKey = 0; yKey < 3; yKey++) { // check first three rows
|
|
xShift = KeybKeypad[yKey][0];
|
|
xNum = KeybKeypad[yKey][1];
|
|
for (xKey = 2; xKey < xNum; xKey++) {
|
|
xm = x0 + xShift + 8 + (46 * (xKey - 2));
|
|
xM = xm + 40;
|
|
ym = y0 + (50 * yKey);
|
|
yM = ym + 40;
|
|
if (isInside(x, y, xm, xM, ym, yM)) {
|
|
return KeybKeypad[yKey][xKey];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default: // keyboard
|
|
ym = y0 + 90; // last row
|
|
yM = ym + 25;
|
|
xm = x0 + 52; // Spacebar
|
|
xM = xm + 130;
|
|
if (isInside(x, y, xm, xM, ym, yM))
|
|
return ' ';
|
|
xm = x0 + 5; // Num
|
|
xM = xm + 40;
|
|
if (isInside(x, y, xm, xM, ym, yM)) {
|
|
keybData[id].type = KEYB_NUM;
|
|
return CHR_REDRAW;
|
|
}
|
|
xm = x0 + 189; // Sym
|
|
xM = xm + 45;
|
|
if (isInside(x, y, xm, xM, ym, yM)) {
|
|
keybData[id].type = KEYB_SYM;
|
|
return CHR_REDRAW;
|
|
}
|
|
ym = y0 + 60; // third row
|
|
yM = ym + 25;
|
|
xm = x0 + 200; // Backspace
|
|
xM = xm + 30;
|
|
if (isInside(x, y, xm, xM, ym, yM))
|
|
return CHR_BKSPC;
|
|
xm = x0 + 5; // ShiftKey
|
|
xM = xm + 30;
|
|
if (isInside(x, y, xm, xM, ym, yM)) {
|
|
keybData[id].type = (keybData[id].type == KEYB_CAPS) ? KEYB_ALPHA : KEYB_CAPS;
|
|
return CHR_REDRAW;
|
|
}
|
|
for (yKey = 0; yKey < 3; yKey++) { // check first three rows
|
|
switch (keybData[id].type) {
|
|
case KEYB_ALPHA:
|
|
xShift = KeybAlpha[yKey][0];
|
|
xNum = KeybAlpha[yKey][1];
|
|
break;
|
|
case KEYB_NUM:
|
|
xShift = KeybNum[yKey][0];
|
|
xNum = KeybNum[yKey][1];
|
|
break;
|
|
case KEYB_SYM:
|
|
xShift = KeybSym[yKey][0];
|
|
xNum = KeybSym[yKey][1];
|
|
break;
|
|
default:
|
|
xShift = KeybAlphaCaps[yKey][0];
|
|
xNum = KeybAlphaCaps[yKey][1];
|
|
break;
|
|
}
|
|
for (xKey = 2; xKey < xNum; xKey++) {
|
|
xm = x0 + xShift + 8 + (23 * (xKey - 2));
|
|
xM = xm + 20;
|
|
ym = y0 + (30 * yKey);
|
|
yM = ym + 25;
|
|
if (isInside(x, y, xm, xM, ym, yM)) {
|
|
switch (keybData[id].type) {
|
|
case KEYB_ALPHA:
|
|
return KeybAlpha[yKey][xKey];
|
|
break;
|
|
case KEYB_NUM:
|
|
return KeybNum[yKey][xKey];
|
|
break;
|
|
case KEYB_SYM:
|
|
return KeybSym[yKey][xKey];
|
|
break;
|
|
default:
|
|
return KeybAlphaCaps[yKey][xKey];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return 0; // no key
|
|
}
|
|
|
|
|
|
bool isInside (uint16_t x, uint16_t y, uint16_t xm, uint16_t xM, uint16_t ym, uint16_t yM) {
|
|
return ((x >= xm) && (x < xM) && (y >= ym) && (y < yM));
|
|
}
|
|
|
|
|
|
|
|
void printObjStack() {
|
|
#ifdef DEBUG
|
|
return;
|
|
for (uint16_t n = 0; n < (endObjStack + 1); n++) {
|
|
Serial.print(objStack[n].objType);
|
|
Serial.print("(");
|
|
Serial.print(objStack[n].objID);
|
|
Serial.print("), ");
|
|
}
|
|
Serial.println("<- Stack");
|
|
#endif
|
|
}
|