mirror of
https://github.com/meshtastic/firmware.git
synced 2025-09-05 19:19:18 +00:00
Merge 4d9dbce55f
into 8a8f60d129
This commit is contained in:
commit
575178d4ac
285
src/graphics/VirtualKeyboard.cpp
Normal file
285
src/graphics/VirtualKeyboard.cpp
Normal file
@ -0,0 +1,285 @@
|
||||
/*
|
||||
* VirtualKeyboard.cpp
|
||||
* Author TSAO (hey@tsao.dev) 2025
|
||||
*/
|
||||
|
||||
#include "VirtualKeyboard.h"
|
||||
|
||||
#ifdef MESHCORE
|
||||
#include "MeshCore.h"
|
||||
#elif defined(MESHTASTIC)
|
||||
#include "configuration.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#endif
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef MESHCORE
|
||||
VirtualKeyboard::VirtualKeyboard(DisplayDriver *displayDriver, int startX, int startY, int keyboardWidth,
|
||||
int keyboardHeight)
|
||||
: display(displayDriver) {
|
||||
#elif defined(MESHTASTIC)
|
||||
VirtualKeyboard::VirtualKeyboard(OLEDDisplay *displayDriver, int startX, int startY, int keyboardWidth,
|
||||
int keyboardHeight)
|
||||
: display(displayDriver) {
|
||||
#endif
|
||||
memset(inputBuffer, 0, sizeof(inputBuffer));
|
||||
bufferLength = 0;
|
||||
|
||||
this->startX = startX;
|
||||
this->startY = startY;
|
||||
this->keyboardWidth = keyboardWidth;
|
||||
this->keyboardHeight = keyboardHeight;
|
||||
}
|
||||
|
||||
void VirtualKeyboard::moveCursor(int deltaRow, int deltaColumn) {
|
||||
cursorRow += deltaRow;
|
||||
cursorColumn += deltaColumn;
|
||||
|
||||
// Handle column overflow/underflow with row wrapping
|
||||
if (cursorColumn >= getCols()) {
|
||||
cursorColumn = 0;
|
||||
cursorRow++;
|
||||
} else if (cursorColumn < 0) {
|
||||
cursorColumn = getCols() - 1;
|
||||
cursorRow--;
|
||||
}
|
||||
|
||||
// Handle row overflow/underflow
|
||||
if (cursorRow >= getRows()) {
|
||||
cursorRow = 0;
|
||||
} else if (cursorRow < 0) {
|
||||
cursorRow = getRows() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualKeyboard::moveRight(int steps) {
|
||||
moveCursor(0, steps);
|
||||
}
|
||||
|
||||
void VirtualKeyboard::moveLeft(int steps) {
|
||||
moveCursor(0, -steps);
|
||||
}
|
||||
|
||||
void VirtualKeyboard::moveUp(int steps) {
|
||||
moveCursor(-steps, 0);
|
||||
}
|
||||
|
||||
void VirtualKeyboard::moveDown(int steps) {
|
||||
moveCursor(steps, 0);
|
||||
}
|
||||
|
||||
const char *VirtualKeyboard::getKeyText(int row, int column) {
|
||||
const char *keyString = getKeyAt(row, column);
|
||||
if (!keyString || strlen(keyString) == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Handle caps lock for letters
|
||||
if (capsLockEnabled && strlen(keyString) == 1 && keyString[0] >= 'a' && keyString[0] <= 'z') {
|
||||
static char uppercaseKey[2];
|
||||
uppercaseKey[0] = keyString[0] - 'a' + 'A';
|
||||
uppercaseKey[1] = '\0';
|
||||
return uppercaseKey;
|
||||
}
|
||||
|
||||
return keyString;
|
||||
}
|
||||
|
||||
const char *VirtualKeyboard::pressCurrentKey() {
|
||||
const char *pressedKey = getKeyText(cursorRow, cursorColumn);
|
||||
|
||||
if (strcmp(pressedKey, "DEL") == 0) {
|
||||
// Delete/Backspace
|
||||
if (bufferLength > 0) {
|
||||
bufferLength--;
|
||||
inputBuffer[bufferLength] = '\0';
|
||||
}
|
||||
} else if (strcmp(pressedKey, "OK") == 0) {
|
||||
// Enter - could trigger submission or new line
|
||||
if (bufferLength < sizeof(inputBuffer) - 1) {
|
||||
inputBuffer[bufferLength++] = '\n';
|
||||
inputBuffer[bufferLength] = '\0';
|
||||
}
|
||||
} else if (strcmp(pressedKey, "SPACE") == 0) {
|
||||
// Space
|
||||
if (bufferLength < sizeof(inputBuffer) - 1) {
|
||||
inputBuffer[bufferLength++] = ' ';
|
||||
inputBuffer[bufferLength] = '\0';
|
||||
}
|
||||
} else if (strcmp(pressedKey, "CAPS") == 0) {
|
||||
// Toggle caps lock
|
||||
capsLockEnabled = !capsLockEnabled;
|
||||
} else {
|
||||
// Regular character
|
||||
int keyLength = strlen(pressedKey);
|
||||
if (bufferLength + keyLength < sizeof(inputBuffer) - 1) {
|
||||
strcat(inputBuffer, pressedKey);
|
||||
bufferLength += keyLength;
|
||||
}
|
||||
}
|
||||
|
||||
// Call user callback if one is set
|
||||
if (keyPressCallback != nullptr) {
|
||||
keyPressCallback(pressedKey, inputBuffer);
|
||||
}
|
||||
|
||||
return pressedKey;
|
||||
}
|
||||
|
||||
void VirtualKeyboard::clear() {
|
||||
memset(inputBuffer, 0, sizeof(inputBuffer));
|
||||
bufferLength = 0;
|
||||
}
|
||||
|
||||
void VirtualKeyboard::reset() {
|
||||
clear();
|
||||
cursorRow = 0;
|
||||
cursorColumn = 0;
|
||||
}
|
||||
|
||||
void VirtualKeyboard::setKeyPressCallback(KeyPressCallback callback) {
|
||||
keyPressCallback = callback;
|
||||
}
|
||||
|
||||
void VirtualKeyboard::getTextBounds(const char *text, uint16_t *width, uint16_t *height) {
|
||||
if (!display || !text) {
|
||||
if (width) *width = 0;
|
||||
if (height) *height = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef MESHCORE
|
||||
Adafruit_SSD1306 *ssd1306Display = static_cast<Adafruit_SSD1306 *>(display->getDisplay());
|
||||
if (ssd1306Display) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
ssd1306Display->getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||
if (width) *width = w;
|
||||
if (height) *height = h;
|
||||
} else {
|
||||
// Fallback if getDisplay() doesn't work
|
||||
if (width) *width = display->getTextWidth(text);
|
||||
if (height) *height = 8; // Default text height
|
||||
}
|
||||
#elif defined(MESHTASTIC)
|
||||
if (width) *width = display->getStringWidth(text);
|
||||
// FONT_SMALL is 7 by 🧐
|
||||
if (height) *height = 7; // FONT_HEIGHT_SMALL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VirtualKeyboard::drawKeyboard() {
|
||||
if (!display) return;
|
||||
|
||||
#ifdef MESHCORE
|
||||
display->setTextSize(1);
|
||||
#elif defined(MESHTASTIC)
|
||||
display->setFont(FONT_SMALL);
|
||||
// display->clear();
|
||||
#endif
|
||||
|
||||
// Calculate max standard key width (kw)
|
||||
uint16_t kw = 0;
|
||||
for (int row = 0; row < getRows(); row++) {
|
||||
for (int col = 0; col < getCols() - 1; col++) { // Exclude rightmost column (control keys)
|
||||
uint16_t keyWidth = 0;
|
||||
const char *keyText = getKeyText(row, col);
|
||||
getTextBounds(keyText, &keyWidth, nullptr);
|
||||
if (keyWidth > kw) {
|
||||
kw = keyWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate max control key width (cw)
|
||||
uint16_t cw = 0;
|
||||
for (int row = 0; row < getRows(); row++) {
|
||||
int col = getCols() - 1; // Only check rightmost column
|
||||
uint16_t keyWidth = 0;
|
||||
const char *keyText = getKeyText(row, col);
|
||||
getTextBounds(keyText, &keyWidth, nullptr);
|
||||
if (keyWidth > cw) {
|
||||
cw = keyWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate horizontal spacing
|
||||
uint16_t totalKeyWidth = (getCols() - 1) * kw + cw;
|
||||
uint16_t spacingX = (keyboardWidth - totalKeyWidth) / (getCols() - 1);
|
||||
uint16_t fraction = (keyboardWidth - totalKeyWidth) % (getCols() - 1);
|
||||
|
||||
// Calculate key height and vertical spacing
|
||||
uint16_t keyH = 0;
|
||||
getTextBounds(getKeyText(0, 0), nullptr, &keyH);
|
||||
uint16_t spacingY = (keyboardHeight - getRows() * keyH) / (getRows() - 1);
|
||||
|
||||
#ifdef MESHTASTIC
|
||||
spacingY = 2;
|
||||
#endif
|
||||
|
||||
for (int row = 0; row < getRows(); row++) {
|
||||
for (int col = 0; col < getCols(); col++) {
|
||||
const char *label = getKeyText(row, col);
|
||||
|
||||
uint16_t currentKeyWidth = (col == getCols() - 1) ? cw : kw;
|
||||
|
||||
// Calculate x position dynamically
|
||||
int currentX = startX + col * (kw + spacingX) + ((col == getCols() - 1) ? (cw - kw) : 0);
|
||||
|
||||
if (col == getCols() - 1) {
|
||||
currentX = keyboardWidth - cw;
|
||||
// currentX += fraction * (getCols() - 1);
|
||||
}
|
||||
|
||||
// Calculate y position
|
||||
int y = startY + row * keyH + row * spacingY;
|
||||
|
||||
// Check if this is the currently selected key
|
||||
bool selected = (row == cursorRow && col == cursorColumn);
|
||||
|
||||
if (selected) {
|
||||
// Highlight the selected key with inverted colors
|
||||
#ifdef MESHCORE
|
||||
display->setColor(DisplayDriver::LIGHT);
|
||||
display->fillRect(currentX - 1, y - 1, currentKeyWidth + 2, keyH + 2);
|
||||
display->setColor(DisplayDriver::DARK); // Dark text on light background
|
||||
#elif defined(MESHTASTIC)
|
||||
display->setColor(OLEDDISPLAY_COLOR::WHITE);
|
||||
if (col == 0 && startX > 0) {
|
||||
display->fillRect(currentX - 1, y + 2, currentKeyWidth + 1, keyH + 2);
|
||||
} else {
|
||||
display->fillRect(currentX - 2, y + 2, currentKeyWidth + 1, keyH + 2);
|
||||
}
|
||||
display->setColor(OLEDDISPLAY_COLOR::BLACK); // Dark text on light background
|
||||
#endif
|
||||
} else {
|
||||
#ifdef MESHCORE
|
||||
display->setColor(DisplayDriver::LIGHT); // Light text on dark background
|
||||
#elif defined(MESHTASTIC)
|
||||
display->setColor(OLEDDISPLAY_COLOR::WHITE); // Light text on dark background
|
||||
#endif
|
||||
}
|
||||
|
||||
// Draw the key text at the key position
|
||||
#ifdef MESHCORE
|
||||
display->setCursor(currentX, y);
|
||||
display->print(label);
|
||||
#elif defined(MESHTASTIC)
|
||||
display->drawString(currentX, y, label);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Reset text color to default
|
||||
#ifdef MESHCORE
|
||||
display->setColor(DisplayDriver::LIGHT);
|
||||
#elif defined(MESHTASTIC)
|
||||
display->setColor(OLEDDISPLAY_COLOR::WHITE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void VirtualKeyboard::render() {
|
||||
drawKeyboard();
|
||||
}
|
108
src/graphics/VirtualKeyboard.h
Normal file
108
src/graphics/VirtualKeyboard.h
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* VirtualKeyboard.h
|
||||
* Author TSAO (hey@tsao.dev) 2025
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef MESHCORE
|
||||
#include <Adafruit_SSD1306.h>
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
#elif defined(MESHTASTIC)
|
||||
#include "graphics/Screen.h"
|
||||
|
||||
#include <OLEDDisplay.h>
|
||||
#endif
|
||||
|
||||
// Callback function type for key press events
|
||||
typedef void (*KeyPressCallback)(const char *pressedKey, const char *currentText);
|
||||
|
||||
#ifdef VIRTUAL_KEYBOARD_EN
|
||||
static const int VIRTUAL_KEYBOARD_ROWS = 4;
|
||||
static const int VIRTUAL_KEYBOARD_COLS = 11;
|
||||
|
||||
static const char *keyboardLayout[VIRTUAL_KEYBOARD_ROWS][VIRTUAL_KEYBOARD_COLS] = {
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "DEL" },
|
||||
{ "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "OK" },
|
||||
{ "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "SPACE" },
|
||||
{ "z", "x", "c", "v", "b", "n", "m", ".", ",", "?", "CAPS" },
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef VIRTUAL_KEYBOARD_CN
|
||||
static const int VIRTUAL_KEYBOARD_ROWS = 4;
|
||||
static const int VIRTUAL_KEYBOARD_COLS = 11;
|
||||
|
||||
static const char *keyboardLayout[VIRTUAL_KEYBOARD_ROWS][VIRTUAL_KEYBOARD_COLS] = {
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "DEL" },
|
||||
{ "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "OK" },
|
||||
{ "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "SPACE" },
|
||||
{ "z", "x", "c", "v", "b", "n", "m", ".", ",", "?", "EN/CN" },
|
||||
};
|
||||
#endif
|
||||
|
||||
class VirtualKeyboard {
|
||||
private:
|
||||
#ifdef MESHCORE
|
||||
DisplayDriver *display;
|
||||
#elif defined(MESHTASTIC)
|
||||
OLEDDisplay *display;
|
||||
#endif
|
||||
|
||||
int cursorRow = 0;
|
||||
int cursorColumn = 0;
|
||||
bool capsLockEnabled = false;
|
||||
|
||||
char inputBuffer[160];
|
||||
int bufferLength = 0;
|
||||
|
||||
// Keyboard positioning and dimensions
|
||||
int startX = 0;
|
||||
int startY = 0;
|
||||
int keyboardWidth = 0;
|
||||
int keyboardHeight = 0;
|
||||
|
||||
// Callback function for key press events
|
||||
KeyPressCallback keyPressCallback = nullptr;
|
||||
|
||||
void drawKeyboard();
|
||||
const char *getKeyText(int row, int col);
|
||||
|
||||
public:
|
||||
#ifdef MESHCORE
|
||||
VirtualKeyboard(DisplayDriver *display, int startX = 0, int startY = 0, int keyboardWidth = 0,
|
||||
int keyboardHeight = 0);
|
||||
#elif defined(MESHTASTIC)
|
||||
VirtualKeyboard(OLEDDisplay *display, int startX = 0, int startY = 0, int keyboardWidth = 0,
|
||||
int keyboardHeight = 0);
|
||||
#endif
|
||||
|
||||
void moveCursor(int deltaRow, int deltaColumn);
|
||||
void moveRight(int steps = 1);
|
||||
void moveLeft(int steps = 1);
|
||||
void moveUp(int steps = 1);
|
||||
void moveDown(int steps = 1);
|
||||
|
||||
const char *pressCurrentKey();
|
||||
void clear();
|
||||
void reset();
|
||||
|
||||
const char *getText() const { return inputBuffer; }
|
||||
int getTextLength() const { return bufferLength; }
|
||||
|
||||
// Usage: kb.setKeyPressCallback([](const char* key, const char* text) { ... });
|
||||
void setKeyPressCallback(KeyPressCallback callback);
|
||||
|
||||
void getTextBounds(const char *text, uint16_t *width, uint16_t *height);
|
||||
void render();
|
||||
|
||||
int getRows() const { return VIRTUAL_KEYBOARD_ROWS; }
|
||||
int getCols() const { return VIRTUAL_KEYBOARD_COLS; }
|
||||
|
||||
const char *getKeyAt(int row, int col) const {
|
||||
if (row >= 0 && row < VIRTUAL_KEYBOARD_ROWS && col >= 0 && col < VIRTUAL_KEYBOARD_COLS) {
|
||||
return keyboardLayout[row][col];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user