mirror of
https://github.com/justcallmekoko/ESP32Marauder.git
synced 2025-12-05 20:40:25 -08:00
402 lines
11 KiB
C++
402 lines
11 KiB
C++
#include "TouchKeyboard.h"
|
||
#include <string.h>
|
||
#include <Arduino.h>
|
||
|
||
#ifdef HAS_TOUCH
|
||
|
||
extern Display display_obj;
|
||
|
||
// Keyboard will occupy the bottom half of the screen.
|
||
static inline int16_t kbHeight() { return TFT_HEIGHT / 2; }
|
||
static inline int16_t kbYStart() { return TFT_HEIGHT - kbHeight(); }
|
||
static inline int16_t kbWidth() { return TFT_WIDTH; }
|
||
static inline int16_t kbXStart() { return 0; }
|
||
|
||
static const int KEY_ROWS = 5;
|
||
|
||
static const char ROW0_ALPHA[] = "1234567890";
|
||
static const char ROW1_ALPHA[] = "qwertyuiop";
|
||
static const char ROW2_ALPHA[] = "asdfghjkl";
|
||
static const char ROW3_ALPHA[] = "zxcvbnm.";
|
||
|
||
static const char ROW0_SYM[] = "!@#$%^&*()";
|
||
static const char ROW1_SYM[] = "`~-_=+[]{}";
|
||
static const char ROW2_SYM[] = "\\|;:'\"";
|
||
static const char ROW3_SYM[] = ",.<>/?";
|
||
|
||
static const uint16_t TOUCH_THRESHOLD = 600;
|
||
|
||
enum KeyboardLayout {
|
||
LAYOUT_ALPHA = 0,
|
||
LAYOUT_SYMBOLS
|
||
};
|
||
|
||
static void drawTextArea(const char *title, const char *buffer) {
|
||
int16_t areaHeight = TFT_HEIGHT - kbHeight();
|
||
|
||
// Clear text area
|
||
display_obj.tft.fillRect(0, 0, TFT_WIDTH, areaHeight, TFT_BLACK);
|
||
|
||
int16_t cursorY = 2;
|
||
|
||
// Optional title
|
||
if (title && title[0] != '\0') {
|
||
display_obj.tft.setCursor(2, cursorY);
|
||
display_obj.tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||
display_obj.tft.print(title);
|
||
cursorY += 16;
|
||
}
|
||
|
||
// Draw current text
|
||
display_obj.tft.setCursor(2, cursorY);
|
||
display_obj.tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||
display_obj.tft.print(buffer ? buffer : "");
|
||
}
|
||
|
||
static void drawKeyboard(KeyboardLayout layout, bool caps) {
|
||
const int16_t kY = kbYStart();
|
||
const int16_t kH = kbHeight();
|
||
const int16_t kX = kbXStart();
|
||
const int16_t kW = kbWidth();
|
||
|
||
const int maxCols = 10;
|
||
const int rows = KEY_ROWS;
|
||
|
||
const int16_t cellW = kW / maxCols;
|
||
const int16_t cellH = kH / rows;
|
||
|
||
display_obj.tft.fillRect(kX, kY, kW, kH, TFT_DARKGREY);
|
||
display_obj.tft.setTextColor(TFT_BLACK, TFT_DARKGREY);
|
||
|
||
// Choose row strings based on layout
|
||
const char *rowStringsAlpha[4] = {
|
||
ROW0_ALPHA, ROW1_ALPHA, ROW2_ALPHA, ROW3_ALPHA
|
||
};
|
||
const char *rowStringsSym[4] = {
|
||
ROW0_SYM, ROW1_SYM, ROW2_SYM, ROW3_SYM
|
||
};
|
||
|
||
const char **rowsPtr = (layout == LAYOUT_ALPHA)
|
||
? (const char **)rowStringsAlpha
|
||
: (const char **)rowStringsSym;
|
||
|
||
// Draw normal character rows (0–3)
|
||
for (int r = 0; r < 4; ++r) {
|
||
const char *row = rowsPtr[r];
|
||
|
||
// For alpha layout row 3, we will also draw CAPS on the rightmost 2 columns.
|
||
// The textual row only covers the first part.
|
||
int rowLen = strlen(row);
|
||
|
||
int16_t rowY = kY + r * cellH;
|
||
|
||
// For alpha row 3, we want row chars towards the left, leaving space for CAPS.
|
||
int16_t xOffset;
|
||
if (layout == LAYOUT_ALPHA && r == 3) {
|
||
// Use columns 0..7 for characters, 8..9 for CAPS
|
||
xOffset = 0;
|
||
} else {
|
||
int maxColsRow = maxCols;
|
||
xOffset = (maxColsRow - rowLen);
|
||
if (xOffset < 0) xOffset = 0;
|
||
xOffset = (xOffset * cellW) / 2; // center row
|
||
}
|
||
|
||
for (int i = 0; i < rowLen; ++i) {
|
||
int16_t keyX = kX + xOffset + i * cellW;
|
||
int16_t keyY = rowY;
|
||
|
||
// Key border
|
||
display_obj.tft.drawRect(keyX, keyY, cellW, cellH, TFT_BLACK);
|
||
|
||
// Label
|
||
display_obj.tft.setCursor(keyX + cellW / 2 - 3, keyY + cellH / 2 - 4);
|
||
char c = row[i];
|
||
|
||
// Apply CAPS on alpha letters
|
||
if (layout == LAYOUT_ALPHA && r > 0) {
|
||
if (c >= 'a' && c <= 'z') {
|
||
if (caps) {
|
||
c = (char)(c - 'a' + 'A');
|
||
}
|
||
}
|
||
}
|
||
|
||
char s[2] = { c, '\0' };
|
||
display_obj.tft.print(s);
|
||
}
|
||
|
||
// Draw CAPS key for alpha layout on row 3 (rightmost two columns)
|
||
if (layout == LAYOUT_ALPHA && r == 3) {
|
||
int16_t capsX = kX + 8 * cellW;
|
||
int16_t capsW = 2 * cellW;
|
||
|
||
display_obj.tft.drawRect(capsX, rowY, capsW, cellH, TFT_BLACK);
|
||
display_obj.tft.setCursor(capsX + 4, rowY + cellH / 2 - 4);
|
||
// Show different label based on state
|
||
if (caps) {
|
||
display_obj.tft.print("caps");
|
||
} else {
|
||
display_obj.tft.print("CAPS");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Special row (row index 4): CANCEL | SYMB/ABC | SPACE | BKSP | OK
|
||
int r = 4;
|
||
int16_t rowY = kY + r * cellH;
|
||
|
||
// Divide width into 6 segments:
|
||
// [CANCEL][SYMB][SPACE (2 segments)][BKSP][OK]
|
||
int16_t segW = kW / 6;
|
||
int16_t x0 = kX;
|
||
int16_t x1 = x0 + segW; // end CANCEL
|
||
int16_t x2 = x1 + segW; // end SYMB
|
||
int16_t x3 = x2 + 2 * segW; // end SPACE
|
||
int16_t x4 = x3 + segW; // end BKSP
|
||
int16_t x5 = x4 + segW; // end OK
|
||
|
||
// CANCEL
|
||
display_obj.tft.drawRect(x0, rowY, segW, cellH, TFT_BLACK);
|
||
display_obj.tft.setCursor(x0 + 4, rowY + cellH / 2 - 4);
|
||
display_obj.tft.print("CANCEL");
|
||
|
||
// SYMB / ABC
|
||
display_obj.tft.drawRect(x1, rowY, segW, cellH, TFT_BLACK);
|
||
display_obj.tft.setCursor(x1 + 4, rowY + cellH / 2 - 4);
|
||
if (layout == LAYOUT_ALPHA) {
|
||
display_obj.tft.print("SYMB");
|
||
} else {
|
||
display_obj.tft.print("ABC");
|
||
}
|
||
|
||
display_obj.tft.drawRect(x2, rowY, (x3 - x2), cellH, TFT_BLACK);
|
||
display_obj.tft.setCursor(x2 + 4, rowY + cellH / 2 - 4);
|
||
display_obj.tft.print("SPACE");
|
||
|
||
display_obj.tft.drawRect(x3, rowY, (x4 - x3), cellH, TFT_BLACK);
|
||
display_obj.tft.setCursor(x3 + 4, rowY + cellH / 2 - 4);
|
||
display_obj.tft.print("BKSP");
|
||
|
||
display_obj.tft.drawRect(x4, rowY, (x5 - x4), cellH, TFT_BLACK);
|
||
display_obj.tft.setCursor(x4 + 4, rowY + cellH / 2 - 4);
|
||
display_obj.tft.print("OK");
|
||
}
|
||
|
||
static bool appendChar(char *buffer, size_t bufLen, char c) {
|
||
size_t len = strlen(buffer);
|
||
if (len + 1 < bufLen) {
|
||
buffer[len] = c;
|
||
buffer[len + 1] = '\0';
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static KeyboardResult handleKeyboardTouch(uint16_t tx, uint16_t ty,
|
||
char *buffer, size_t bufLen,
|
||
KeyboardLayout layout,
|
||
bool caps) {
|
||
if (!buffer || bufLen < 2) return KB_NONE;
|
||
|
||
const int16_t kY = kbYStart();
|
||
const int16_t kH = kbHeight();
|
||
const int16_t kX = kbXStart();
|
||
const int16_t kW = kbWidth();
|
||
|
||
if (ty < kY || ty >= (kY + kH)) {
|
||
// Touch is outside keyboard area
|
||
return KB_NONE;
|
||
}
|
||
|
||
const int maxCols = 10;
|
||
const int rows = KEY_ROWS;
|
||
const int16_t cellW = kW / maxCols;
|
||
const int16_t cellH = kH / rows;
|
||
|
||
int rowIndex = (ty - kY) / cellH;
|
||
|
||
// Choose row strings based on layout
|
||
const char *rowStringsAlpha[4] = {
|
||
ROW0_ALPHA, ROW1_ALPHA, ROW2_ALPHA, ROW3_ALPHA
|
||
};
|
||
const char *rowStringsSym[4] = {
|
||
ROW0_SYM, ROW1_SYM, ROW2_SYM, ROW3_SYM
|
||
};
|
||
|
||
const char **rowsPtr = (layout == LAYOUT_ALPHA)
|
||
? (const char **)rowStringsAlpha
|
||
: (const char **)rowStringsSym;
|
||
|
||
// Normal rows (0..3)
|
||
if (rowIndex >= 0 && rowIndex <= 3) {
|
||
const char *row = rowsPtr[rowIndex];
|
||
int rowLen = strlen(row);
|
||
|
||
int16_t rowY = kY + rowIndex * cellH;
|
||
|
||
// Alpha row 3: characters on columns 0..7, CAPS on columns 8..9
|
||
if (layout == LAYOUT_ALPHA && rowIndex == 3) {
|
||
int16_t capsXStart = kX + 8 * cellW;
|
||
int16_t capsXEnd = capsXStart + 2 * cellW;
|
||
|
||
// Check CAPS region
|
||
if (tx >= capsXStart && tx < capsXEnd) {
|
||
return KB_TOGGLE_CAPS;
|
||
}
|
||
|
||
// Characters only in the columns before CAPS
|
||
int16_t xOffset = 0;
|
||
if (tx < kX || tx >= (kX + 8 * cellW)) {
|
||
return KB_NONE;
|
||
}
|
||
|
||
int colIndex = (tx - (kX + xOffset)) / cellW;
|
||
if (colIndex < 0 || colIndex >= rowLen) return KB_NONE;
|
||
|
||
char c = row[colIndex];
|
||
// Apply caps mapping
|
||
if (c >= 'a' && c <= 'z' && caps) {
|
||
c = (char)(c - 'a' + 'A');
|
||
}
|
||
|
||
if (appendChar(buffer, bufLen, c)) {
|
||
return KB_CHANGED;
|
||
}
|
||
return KB_NONE;
|
||
}
|
||
|
||
// All other rows
|
||
int16_t xOffset = (maxCols - rowLen);
|
||
if (xOffset < 0) xOffset = 0;
|
||
xOffset = (xOffset * cellW) / 2;
|
||
|
||
if (tx < kX + xOffset || tx >= kX + xOffset + rowLen * cellW) {
|
||
return KB_NONE;
|
||
}
|
||
|
||
int colIndex = (tx - (kX + xOffset)) / cellW;
|
||
if (colIndex < 0 || colIndex >= rowLen) return KB_NONE;
|
||
|
||
char c = row[colIndex];
|
||
|
||
// Apply caps on alpha letters (rows 1 and 2)
|
||
if (layout == LAYOUT_ALPHA && rowIndex > 0) {
|
||
if (c >= 'a' && c <= 'z' && caps) {
|
||
c = (char)(c - 'a' + 'A');
|
||
}
|
||
}
|
||
|
||
if (appendChar(buffer, bufLen, c)) {
|
||
return KB_CHANGED;
|
||
}
|
||
return KB_NONE;
|
||
}
|
||
|
||
// Special row (rowIndex == 4): CANCEL | SYMB/ABC | SPACE | BKSP | OK
|
||
if (rowIndex == 4) {
|
||
int16_t rowY = kY + rowIndex * cellH;
|
||
|
||
int16_t segW = kW / 6;
|
||
int16_t x0 = kX;
|
||
int16_t x1 = x0 + segW; // end CANCEL
|
||
int16_t x2 = x1 + segW; // end SYMB
|
||
int16_t x3 = x2 + 2 * segW; // end SPACE
|
||
int16_t x4 = x3 + segW; // end BKSP
|
||
int16_t x5 = x4 + segW; // end OK
|
||
|
||
// CANCEL
|
||
if (tx >= x0 && tx < x1) {
|
||
return KB_CANCEL;
|
||
}
|
||
|
||
// SYMB / ABC toggle
|
||
if (tx >= x1 && tx < x2) {
|
||
return KB_TOGGLE_LAYOUT;
|
||
}
|
||
|
||
// SPACE (x2..x3)
|
||
if (tx >= x2 && tx < x3) {
|
||
if (appendChar(buffer, bufLen, ' ')) {
|
||
return KB_CHANGED;
|
||
}
|
||
return KB_NONE;
|
||
}
|
||
|
||
// BKSP (x3..x4)
|
||
if (tx >= x3 && tx < x4) {
|
||
size_t len = strlen(buffer);
|
||
if (len > 0) {
|
||
buffer[len - 1] = '\0';
|
||
return KB_CHANGED;
|
||
}
|
||
return KB_NONE;
|
||
}
|
||
|
||
// OK (x4..x5)
|
||
if (tx >= x4 && tx < x5) {
|
||
return KB_DONE;
|
||
}
|
||
}
|
||
|
||
return KB_NONE;
|
||
}
|
||
|
||
|
||
bool keyboardInput(char *buffer, size_t bufLen, const char *title) {
|
||
if (!buffer || bufLen < 2) {
|
||
return false;
|
||
}
|
||
|
||
// To force clear, uncomment this:
|
||
// buffer[0] = '\0';
|
||
|
||
KeyboardLayout layout = LAYOUT_ALPHA;
|
||
bool caps = false;
|
||
|
||
drawTextArea(title, buffer);
|
||
drawKeyboard(layout, caps);
|
||
|
||
uint32_t lastTouchTime = 0;
|
||
const uint32_t debounceMs = 120;
|
||
|
||
while (true) {
|
||
uint16_t x = 0, y = 0;
|
||
uint8_t touched = display_obj.updateTouch(&x, &y, TOUCH_THRESHOLD);
|
||
|
||
if (touched) {
|
||
uint32_t now = millis();
|
||
if (now - lastTouchTime < debounceMs) {
|
||
// "debounce"
|
||
continue;
|
||
}
|
||
lastTouchTime = now;
|
||
|
||
KeyboardResult r = handleKeyboardTouch(x, y, buffer, bufLen, layout, caps);
|
||
|
||
if (r == KB_CHANGED) {
|
||
drawTextArea(title, buffer);
|
||
} else if (r == KB_DONE) {
|
||
drawTextArea(title, buffer);
|
||
return true;
|
||
} else if (r == KB_CANCEL) {
|
||
// Optional: buffer[0] = '\0';
|
||
drawTextArea(title, buffer);
|
||
return false;
|
||
} else if (r == KB_TOGGLE_LAYOUT) {
|
||
layout = (layout == LAYOUT_ALPHA) ? LAYOUT_SYMBOLS : LAYOUT_ALPHA;
|
||
drawKeyboard(layout, caps);
|
||
} else if (r == KB_TOGGLE_CAPS) {
|
||
caps = !caps;
|
||
drawKeyboard(layout, caps);
|
||
}
|
||
}
|
||
|
||
delay(5);
|
||
yield();
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
#endif |