Fix underscore on keyboard and standardize activity (#138)
## Summary * Fix underscore on keyboard * Remove special handling of special row characters * Fix navigating between special row items * Standardize keyboard activity to use standard loop * Fix issue with rendering keyboard non-stop Fixes https://github.com/daveallie/crosspoint-reader/issues/131
This commit is contained in:
parent
9023b262a1
commit
02350c6a9f
@ -187,11 +187,21 @@ void WifiSelectionActivity::selectNetwork(const int index) {
|
||||
if (selectedRequiresPassword) {
|
||||
// Show password entry
|
||||
state = WifiSelectionState::PASSWORD_ENTRY;
|
||||
enterNewActivity(new KeyboardEntryActivity(renderer, inputManager, "Enter WiFi Password",
|
||||
"", // No initial text
|
||||
64, // Max password length
|
||||
false // Show password by default (hard keyboard to use)
|
||||
));
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, inputManager, "Enter WiFi Password",
|
||||
"", // No initial text
|
||||
50, // Y position
|
||||
64, // Max password length
|
||||
false, // Show password by default (hard keyboard to use)
|
||||
[this](const std::string& text) {
|
||||
enteredPassword = text;
|
||||
exitActivity();
|
||||
},
|
||||
[this] {
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
updateRequired = true;
|
||||
exitActivity();
|
||||
}));
|
||||
updateRequired = true;
|
||||
} else {
|
||||
// Connect directly for open networks
|
||||
@ -208,11 +218,6 @@ void WifiSelectionActivity::attemptConnection() {
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
// Get password from keyboard if we just entered it
|
||||
if (subActivity && !usedSavedPassword) {
|
||||
enteredPassword = static_cast<KeyboardEntryActivity*>(subActivity.get())->getText();
|
||||
}
|
||||
|
||||
if (selectedRequiresPassword && !enteredPassword.empty()) {
|
||||
WiFi.begin(selectedSSID.c_str(), enteredPassword.c_str());
|
||||
} else {
|
||||
@ -269,6 +274,11 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check scan progress
|
||||
if (state == WifiSelectionState::SCANNING) {
|
||||
processWifiScanResults();
|
||||
@ -281,24 +291,9 @@ void WifiSelectionActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle password entry state
|
||||
if (state == WifiSelectionState::PASSWORD_ENTRY && subActivity) {
|
||||
const auto keyboard = static_cast<KeyboardEntryActivity*>(subActivity.get());
|
||||
keyboard->handleInput();
|
||||
|
||||
if (keyboard->isComplete()) {
|
||||
attemptConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyboard->isCancelled()) {
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
updateRequired = true;
|
||||
if (state == WifiSelectionState::PASSWORD_ENTRY) {
|
||||
// Reach here once password entry finished in subactivity
|
||||
attemptConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -441,6 +436,10 @@ std::string WifiSelectionActivity::getSignalStrengthIndicator(const int32_t rssi
|
||||
|
||||
void WifiSelectionActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (subActivity) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateRequired) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
@ -461,9 +460,6 @@ void WifiSelectionActivity::render() const {
|
||||
case WifiSelectionState::NETWORK_LIST:
|
||||
renderNetworkList();
|
||||
break;
|
||||
case WifiSelectionState::PASSWORD_ENTRY:
|
||||
renderPasswordEntry();
|
||||
break;
|
||||
case WifiSelectionState::CONNECTING:
|
||||
renderConnecting();
|
||||
break;
|
||||
@ -561,23 +557,6 @@ void WifiSelectionActivity::renderNetworkList() const {
|
||||
renderer.drawButtonHints(UI_FONT_ID, "« Back", "Connect", "", "");
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::renderPasswordEntry() const {
|
||||
// Draw header
|
||||
renderer.drawCenteredText(READER_FONT_ID, 5, "WiFi Password", true, BOLD);
|
||||
|
||||
// Draw network name with good spacing from header
|
||||
std::string networkInfo = "Network: " + selectedSSID;
|
||||
if (networkInfo.length() > 30) {
|
||||
networkInfo.replace(27, networkInfo.length() - 27, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_FONT_ID, 38, networkInfo.c_str(), true, REGULAR);
|
||||
|
||||
// Draw keyboard
|
||||
if (subActivity) {
|
||||
static_cast<KeyboardEntryActivity*>(subActivity.get())->render(58);
|
||||
}
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::renderConnecting() const {
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||
|
||||
@ -10,41 +10,55 @@ const char* const KeyboardEntryActivity::keyboard[NUM_ROWS] = {
|
||||
|
||||
// Keyboard layouts - uppercase/symbols
|
||||
const char* const KeyboardEntryActivity::keyboardShift[NUM_ROWS] = {"~!@#$%^&*()_+", "QWERTYUIOP{}|", "ASDFGHJKL:\"",
|
||||
"ZXCVBNM<>?", "^ _____<OK"};
|
||||
"ZXCVBNM<>?", "SPECIAL ROW"};
|
||||
|
||||
void KeyboardEntryActivity::setText(const std::string& newText) {
|
||||
text = newText;
|
||||
if (maxLength > 0 && text.length() > maxLength) {
|
||||
text.resize(maxLength);
|
||||
}
|
||||
void KeyboardEntryActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<KeyboardEntryActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void KeyboardEntryActivity::reset(const std::string& newTitle, const std::string& newInitialText) {
|
||||
if (!newTitle.empty()) {
|
||||
title = newTitle;
|
||||
void KeyboardEntryActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
render();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
text = newInitialText;
|
||||
selectedRow = 0;
|
||||
selectedCol = 0;
|
||||
shiftActive = false;
|
||||
complete = false;
|
||||
cancelled = false;
|
||||
}
|
||||
|
||||
void KeyboardEntryActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
|
||||
// Reset state when entering the activity
|
||||
complete = false;
|
||||
cancelled = false;
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
// Trigger first update
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&KeyboardEntryActivity::taskTrampoline, "KeyboardEntryActivity",
|
||||
2048, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
}
|
||||
|
||||
void KeyboardEntryActivity::loop() {
|
||||
handleInput();
|
||||
render(10);
|
||||
void KeyboardEntryActivity::onExit() {
|
||||
Activity::onExit();
|
||||
|
||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
int KeyboardEntryActivity::getRowLength(int row) const {
|
||||
int KeyboardEntryActivity::getRowLength(const int row) const {
|
||||
if (row < 0 || row >= NUM_ROWS) return 0;
|
||||
|
||||
// Return actual length of each row based on keyboard layout
|
||||
@ -58,7 +72,7 @@ int KeyboardEntryActivity::getRowLength(int row) const {
|
||||
case 3:
|
||||
return 10; // zxcvbnm,./
|
||||
case 4:
|
||||
return 10; // ^, space (5 wide), backspace, OK (2 wide)
|
||||
return 10; // caps (2 wide), space (5 wide), backspace (2 wide), OK
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -75,8 +89,8 @@ char KeyboardEntryActivity::getSelectedChar() const {
|
||||
|
||||
void KeyboardEntryActivity::handleKeyPress() {
|
||||
// Handle special row (bottom row with shift, space, backspace, done)
|
||||
if (selectedRow == SHIFT_ROW) {
|
||||
if (selectedCol == SHIFT_COL) {
|
||||
if (selectedRow == SPECIAL_ROW) {
|
||||
if (selectedCol >= SHIFT_COL && selectedCol < SPACE_COL) {
|
||||
// Shift toggle
|
||||
shiftActive = !shiftActive;
|
||||
return;
|
||||
@ -90,7 +104,7 @@ void KeyboardEntryActivity::handleKeyPress() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedCol == BACKSPACE_COL) {
|
||||
if (selectedCol >= BACKSPACE_COL && selectedCol < DONE_COL) {
|
||||
// Backspace
|
||||
if (!text.empty()) {
|
||||
text.pop_back();
|
||||
@ -100,7 +114,6 @@ void KeyboardEntryActivity::handleKeyPress() {
|
||||
|
||||
if (selectedCol >= DONE_COL) {
|
||||
// Done button
|
||||
complete = true;
|
||||
if (onComplete) {
|
||||
onComplete(text);
|
||||
}
|
||||
@ -109,42 +122,61 @@ void KeyboardEntryActivity::handleKeyPress() {
|
||||
}
|
||||
|
||||
// Regular character
|
||||
char c = getSelectedChar();
|
||||
if (c != '\0' && c != '^' && c != '_' && c != '<') {
|
||||
if (maxLength == 0 || text.length() < maxLength) {
|
||||
text += c;
|
||||
// Auto-disable shift after typing a letter
|
||||
if (shiftActive && ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
|
||||
shiftActive = false;
|
||||
}
|
||||
const char c = getSelectedChar();
|
||||
if (c == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxLength == 0 || text.length() < maxLength) {
|
||||
text += c;
|
||||
// Auto-disable shift after typing a letter
|
||||
if (shiftActive && ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
|
||||
shiftActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyboardEntryActivity::handleInput() {
|
||||
if (complete || cancelled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
|
||||
void KeyboardEntryActivity::loop() {
|
||||
// Navigation
|
||||
if (inputManager.wasPressed(InputManager::BTN_UP)) {
|
||||
if (selectedRow > 0) {
|
||||
selectedRow--;
|
||||
// Clamp column to valid range for new row
|
||||
int maxCol = getRowLength(selectedRow) - 1;
|
||||
const int maxCol = getRowLength(selectedRow) - 1;
|
||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||
}
|
||||
handled = true;
|
||||
} else if (inputManager.wasPressed(InputManager::BTN_DOWN)) {
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
if (inputManager.wasPressed(InputManager::BTN_DOWN)) {
|
||||
if (selectedRow < NUM_ROWS - 1) {
|
||||
selectedRow++;
|
||||
int maxCol = getRowLength(selectedRow) - 1;
|
||||
const int maxCol = getRowLength(selectedRow) - 1;
|
||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||
}
|
||||
handled = true;
|
||||
} else if (inputManager.wasPressed(InputManager::BTN_LEFT)) {
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
if (inputManager.wasPressed(InputManager::BTN_LEFT)) {
|
||||
// Special bottom row case
|
||||
if (selectedRow == SPECIAL_ROW) {
|
||||
// Bottom row has special key widths
|
||||
if (selectedCol >= SHIFT_COL && selectedCol < SPACE_COL) {
|
||||
// In shift key, do nothing
|
||||
} else if (selectedCol >= SPACE_COL && selectedCol < BACKSPACE_COL) {
|
||||
// In space bar, move to shift
|
||||
selectedCol = SHIFT_COL;
|
||||
} else if (selectedCol >= BACKSPACE_COL && selectedCol < DONE_COL) {
|
||||
// In backspace, move to space
|
||||
selectedCol = SPACE_COL;
|
||||
} else if (selectedCol >= DONE_COL) {
|
||||
// At done button, move to backspace
|
||||
selectedCol = BACKSPACE_COL;
|
||||
}
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedCol > 0) {
|
||||
selectedCol--;
|
||||
} else if (selectedRow > 0) {
|
||||
@ -152,9 +184,31 @@ bool KeyboardEntryActivity::handleInput() {
|
||||
selectedRow--;
|
||||
selectedCol = getRowLength(selectedRow) - 1;
|
||||
}
|
||||
handled = true;
|
||||
} else if (inputManager.wasPressed(InputManager::BTN_RIGHT)) {
|
||||
int maxCol = getRowLength(selectedRow) - 1;
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
if (inputManager.wasPressed(InputManager::BTN_RIGHT)) {
|
||||
const int maxCol = getRowLength(selectedRow) - 1;
|
||||
|
||||
// Special bottom row case
|
||||
if (selectedRow == SPECIAL_ROW) {
|
||||
// Bottom row has special key widths
|
||||
if (selectedCol >= SHIFT_COL && selectedCol < SPACE_COL) {
|
||||
// In shift key, move to space
|
||||
selectedCol = SPACE_COL;
|
||||
} else if (selectedCol >= SPACE_COL && selectedCol < BACKSPACE_COL) {
|
||||
// In space bar, move to backspace
|
||||
selectedCol = BACKSPACE_COL;
|
||||
} else if (selectedCol >= BACKSPACE_COL && selectedCol < DONE_COL) {
|
||||
// In backspace, move to done
|
||||
selectedCol = DONE_COL;
|
||||
} else if (selectedCol >= DONE_COL) {
|
||||
// At done button, do nothing
|
||||
}
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedCol < maxCol) {
|
||||
selectedCol++;
|
||||
} else if (selectedRow < NUM_ROWS - 1) {
|
||||
@ -162,35 +216,34 @@ bool KeyboardEntryActivity::handleInput() {
|
||||
selectedRow++;
|
||||
selectedCol = 0;
|
||||
}
|
||||
handled = true;
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
// Selection
|
||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||
handleKeyPress();
|
||||
handled = true;
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
// Cancel
|
||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
||||
cancelled = true;
|
||||
if (onCancel) {
|
||||
onCancel();
|
||||
}
|
||||
handled = true;
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void KeyboardEntryActivity::render(int startY) const {
|
||||
void KeyboardEntryActivity::render() const {
|
||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||
|
||||
renderer.clearScreen();
|
||||
|
||||
// Draw title
|
||||
renderer.drawCenteredText(UI_FONT_ID, startY, title.c_str(), true, REGULAR);
|
||||
|
||||
// Draw input field
|
||||
int inputY = startY + 22;
|
||||
const int inputY = startY + 22;
|
||||
renderer.drawText(UI_FONT_ID, 10, inputY, "[");
|
||||
|
||||
std::string displayText;
|
||||
@ -204,9 +257,9 @@ void KeyboardEntryActivity::render(int startY) const {
|
||||
displayText += "_";
|
||||
|
||||
// Truncate if too long for display - use actual character width from font
|
||||
int charWidth = renderer.getSpaceWidth(UI_FONT_ID);
|
||||
if (charWidth < 1) charWidth = 8; // Fallback to approximate width
|
||||
int maxDisplayLen = (pageWidth - 40) / charWidth;
|
||||
int approxCharWidth = renderer.getSpaceWidth(UI_FONT_ID);
|
||||
if (approxCharWidth < 1) approxCharWidth = 8; // Fallback to approximate width
|
||||
const int maxDisplayLen = (pageWidth - 40) / approxCharWidth;
|
||||
if (displayText.length() > static_cast<size_t>(maxDisplayLen)) {
|
||||
displayText = "..." + displayText.substr(displayText.length() - maxDisplayLen + 3);
|
||||
}
|
||||
@ -215,22 +268,22 @@ void KeyboardEntryActivity::render(int startY) const {
|
||||
renderer.drawText(UI_FONT_ID, pageWidth - 15, inputY, "]");
|
||||
|
||||
// Draw keyboard - use compact spacing to fit 5 rows on screen
|
||||
int keyboardStartY = inputY + 25;
|
||||
const int keyWidth = 18;
|
||||
const int keyHeight = 18;
|
||||
const int keySpacing = 3;
|
||||
const int keyboardStartY = inputY + 25;
|
||||
constexpr int keyWidth = 18;
|
||||
constexpr int keyHeight = 18;
|
||||
constexpr int keySpacing = 3;
|
||||
|
||||
const char* const* layout = shiftActive ? keyboardShift : keyboard;
|
||||
|
||||
// Calculate left margin to center the longest row (13 keys)
|
||||
int maxRowWidth = KEYS_PER_ROW * (keyWidth + keySpacing);
|
||||
int leftMargin = (pageWidth - maxRowWidth) / 2;
|
||||
constexpr int maxRowWidth = KEYS_PER_ROW * (keyWidth + keySpacing);
|
||||
const int leftMargin = (pageWidth - maxRowWidth) / 2;
|
||||
|
||||
for (int row = 0; row < NUM_ROWS; row++) {
|
||||
int rowY = keyboardStartY + row * (keyHeight + keySpacing);
|
||||
const int rowY = keyboardStartY + row * (keyHeight + keySpacing);
|
||||
|
||||
// Left-align all rows for consistent navigation
|
||||
int startX = leftMargin;
|
||||
const int startX = leftMargin;
|
||||
|
||||
// Handle bottom row (row 4) specially with proper multi-column keys
|
||||
if (row == 4) {
|
||||
@ -240,64 +293,37 @@ void KeyboardEntryActivity::render(int startY) const {
|
||||
int currentX = startX;
|
||||
|
||||
// CAPS key (logical col 0, spans 2 key widths)
|
||||
int capsWidth = 2 * keyWidth + keySpacing;
|
||||
bool capsSelected = (selectedRow == 4 && selectedCol == SHIFT_COL);
|
||||
if (capsSelected) {
|
||||
renderer.drawText(UI_FONT_ID, currentX - 2, rowY, "[");
|
||||
renderer.drawText(UI_FONT_ID, currentX + capsWidth - 4, rowY, "]");
|
||||
}
|
||||
renderer.drawText(UI_FONT_ID, currentX + 2, rowY, shiftActive ? "CAPS" : "caps");
|
||||
currentX += capsWidth + keySpacing;
|
||||
const bool capsSelected = (selectedRow == 4 && selectedCol >= SHIFT_COL && selectedCol < SPACE_COL);
|
||||
renderItemWithSelector(currentX + 2, rowY, shiftActive ? "CAPS" : "caps", capsSelected);
|
||||
currentX += 2 * (keyWidth + keySpacing);
|
||||
|
||||
// Space bar (logical cols 2-6, spans 5 key widths)
|
||||
int spaceWidth = 5 * keyWidth + 4 * keySpacing;
|
||||
bool spaceSelected = (selectedRow == 4 && selectedCol >= SPACE_COL && selectedCol < BACKSPACE_COL);
|
||||
if (spaceSelected) {
|
||||
renderer.drawText(UI_FONT_ID, currentX - 2, rowY, "[");
|
||||
renderer.drawText(UI_FONT_ID, currentX + spaceWidth - 4, rowY, "]");
|
||||
}
|
||||
// Draw centered underscores for space bar
|
||||
int spaceTextX = currentX + (spaceWidth / 2) - 12;
|
||||
renderer.drawText(UI_FONT_ID, spaceTextX, rowY, "_____");
|
||||
currentX += spaceWidth + keySpacing;
|
||||
const bool spaceSelected = (selectedRow == 4 && selectedCol >= SPACE_COL && selectedCol < BACKSPACE_COL);
|
||||
const int spaceTextWidth = renderer.getTextWidth(UI_FONT_ID, "_____");
|
||||
const int spaceXWidth = 5 * (keyWidth + keySpacing);
|
||||
const int spaceXPos = currentX + (spaceXWidth - spaceTextWidth) / 2;
|
||||
renderItemWithSelector(spaceXPos, rowY, "_____", spaceSelected);
|
||||
currentX += spaceXWidth;
|
||||
|
||||
// Backspace key (logical col 7, spans 2 key widths)
|
||||
int bsWidth = 2 * keyWidth + keySpacing;
|
||||
bool bsSelected = (selectedRow == 4 && selectedCol == BACKSPACE_COL);
|
||||
if (bsSelected) {
|
||||
renderer.drawText(UI_FONT_ID, currentX - 2, rowY, "[");
|
||||
renderer.drawText(UI_FONT_ID, currentX + bsWidth - 4, rowY, "]");
|
||||
}
|
||||
renderer.drawText(UI_FONT_ID, currentX + 6, rowY, "<-");
|
||||
currentX += bsWidth + keySpacing;
|
||||
const bool bsSelected = (selectedRow == 4 && selectedCol >= BACKSPACE_COL && selectedCol < DONE_COL);
|
||||
renderItemWithSelector(currentX + 2, rowY, "<-", bsSelected);
|
||||
currentX += 2 * (keyWidth + keySpacing);
|
||||
|
||||
// OK button (logical col 9, spans 2 key widths)
|
||||
int okWidth = 2 * keyWidth + keySpacing;
|
||||
bool okSelected = (selectedRow == 4 && selectedCol >= DONE_COL);
|
||||
if (okSelected) {
|
||||
renderer.drawText(UI_FONT_ID, currentX - 2, rowY, "[");
|
||||
renderer.drawText(UI_FONT_ID, currentX + okWidth - 4, rowY, "]");
|
||||
}
|
||||
renderer.drawText(UI_FONT_ID, currentX + 8, rowY, "OK");
|
||||
|
||||
const bool okSelected = (selectedRow == 4 && selectedCol >= DONE_COL);
|
||||
renderItemWithSelector(currentX + 2, rowY, "OK", okSelected);
|
||||
} else {
|
||||
// Regular rows: render each key individually
|
||||
for (int col = 0; col < getRowLength(row); col++) {
|
||||
int keyX = startX + col * (keyWidth + keySpacing);
|
||||
|
||||
// Get the character to display
|
||||
char c = layout[row][col];
|
||||
const char c = layout[row][col];
|
||||
std::string keyLabel(1, c);
|
||||
const int charWidth = renderer.getTextWidth(UI_FONT_ID, keyLabel.c_str());
|
||||
|
||||
// Draw selection highlight
|
||||
bool isSelected = (row == selectedRow && col == selectedCol);
|
||||
|
||||
if (isSelected) {
|
||||
renderer.drawText(UI_FONT_ID, keyX - 2, rowY, "[");
|
||||
renderer.drawText(UI_FONT_ID, keyX + keyWidth - 4, rowY, "]");
|
||||
}
|
||||
|
||||
renderer.drawText(UI_FONT_ID, keyX + 2, rowY, keyLabel.c_str());
|
||||
const int keyX = startX + col * (keyWidth + keySpacing) + (keyWidth - charWidth) / 2;
|
||||
const bool isSelected = row == selectedRow && col == selectedCol;
|
||||
renderItemWithSelector(keyX, rowY, keyLabel.c_str(), isSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -305,4 +331,15 @@ void KeyboardEntryActivity::render(int startY) const {
|
||||
// Draw help text at absolute bottom of screen (consistent with other screens)
|
||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||
renderer.drawText(SMALL_FONT_ID, 10, pageHeight - 30, "Navigate: D-pad | Select: OK | Cancel: BACK");
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
|
||||
void KeyboardEntryActivity::renderItemWithSelector(const int x, const int y, const char* item,
|
||||
const bool isSelected) const {
|
||||
if (isSelected) {
|
||||
const int itemWidth = renderer.getTextWidth(UI_FONT_ID, item);
|
||||
renderer.drawText(UI_FONT_ID, x - 6, y, "[");
|
||||
renderer.drawText(UI_FONT_ID, x + itemWidth, y, "]");
|
||||
}
|
||||
renderer.drawText(UI_FONT_ID, x, y, item);
|
||||
}
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
#pragma once
|
||||
#include <GfxRenderer.h>
|
||||
#include <InputManager.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "../Activity.h"
|
||||
|
||||
@ -30,80 +34,44 @@ class KeyboardEntryActivity : public Activity {
|
||||
* @param inputManager Reference to InputManager for handling input
|
||||
* @param title Title to display above the keyboard
|
||||
* @param initialText Initial text to show in the input field
|
||||
* @param startY Y position to start rendering the keyboard
|
||||
* @param maxLength Maximum length of input text (0 for unlimited)
|
||||
* @param isPassword If true, display asterisks instead of actual characters
|
||||
* @param onComplete Callback invoked when input is complete
|
||||
* @param onCancel Callback invoked when input is cancelled
|
||||
*/
|
||||
KeyboardEntryActivity(GfxRenderer& renderer, InputManager& inputManager, const std::string& title = "Enter Text",
|
||||
const std::string& initialText = "", const size_t maxLength = 0, const bool isPassword = false)
|
||||
explicit KeyboardEntryActivity(GfxRenderer& renderer, InputManager& inputManager, std::string title = "Enter Text",
|
||||
std::string initialText = "", const int startY = 10, const size_t maxLength = 0,
|
||||
const bool isPassword = false, OnCompleteCallback onComplete = nullptr,
|
||||
OnCancelCallback onCancel = nullptr)
|
||||
: Activity("KeyboardEntry", renderer, inputManager),
|
||||
title(title),
|
||||
text(initialText),
|
||||
title(std::move(title)),
|
||||
text(std::move(initialText)),
|
||||
startY(startY),
|
||||
maxLength(maxLength),
|
||||
isPassword(isPassword) {}
|
||||
|
||||
/**
|
||||
* Handle button input. Call this in your main loop.
|
||||
* @return true if input was handled, false otherwise
|
||||
*/
|
||||
bool handleInput();
|
||||
|
||||
/**
|
||||
* Render the keyboard at the specified Y position.
|
||||
* @param startY Y-coordinate where keyboard rendering starts (default 10)
|
||||
*/
|
||||
void render(int startY = 10) const;
|
||||
|
||||
/**
|
||||
* Get the current text entered by the user.
|
||||
*/
|
||||
const std::string& getText() const { return text; }
|
||||
|
||||
/**
|
||||
* Set the current text.
|
||||
*/
|
||||
void setText(const std::string& newText);
|
||||
|
||||
/**
|
||||
* Check if the user has completed text entry (pressed OK on Done).
|
||||
*/
|
||||
bool isComplete() const { return complete; }
|
||||
|
||||
/**
|
||||
* Check if the user has cancelled text entry.
|
||||
*/
|
||||
bool isCancelled() const { return cancelled; }
|
||||
|
||||
/**
|
||||
* Reset the keyboard state for reuse.
|
||||
*/
|
||||
void reset(const std::string& newTitle = "", const std::string& newInitialText = "");
|
||||
|
||||
/**
|
||||
* Set callback for when input is complete.
|
||||
*/
|
||||
void setOnComplete(OnCompleteCallback callback) { onComplete = callback; }
|
||||
|
||||
/**
|
||||
* Set callback for when input is cancelled.
|
||||
*/
|
||||
void setOnCancel(OnCancelCallback callback) { onCancel = callback; }
|
||||
isPassword(isPassword),
|
||||
onComplete(std::move(onComplete)),
|
||||
onCancel(std::move(onCancel)) {}
|
||||
|
||||
// Activity overrides
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
|
||||
private:
|
||||
std::string title;
|
||||
int startY;
|
||||
std::string text;
|
||||
size_t maxLength;
|
||||
bool isPassword;
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
bool updateRequired = false;
|
||||
|
||||
// Keyboard state
|
||||
int selectedRow = 0;
|
||||
int selectedCol = 0;
|
||||
bool shiftActive = false;
|
||||
bool complete = false;
|
||||
bool cancelled = false;
|
||||
|
||||
// Callbacks
|
||||
OnCompleteCallback onComplete;
|
||||
@ -116,16 +84,17 @@ class KeyboardEntryActivity : public Activity {
|
||||
static const char* const keyboardShift[NUM_ROWS];
|
||||
|
||||
// Special key positions (bottom row)
|
||||
static constexpr int SHIFT_ROW = 4;
|
||||
static constexpr int SPECIAL_ROW = 4;
|
||||
static constexpr int SHIFT_COL = 0;
|
||||
static constexpr int SPACE_ROW = 4;
|
||||
static constexpr int SPACE_COL = 2;
|
||||
static constexpr int BACKSPACE_ROW = 4;
|
||||
static constexpr int BACKSPACE_COL = 7;
|
||||
static constexpr int DONE_ROW = 4;
|
||||
static constexpr int DONE_COL = 9;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
char getSelectedChar() const;
|
||||
void handleKeyPress();
|
||||
int getRowLength(int row) const;
|
||||
void render() const;
|
||||
void renderItemWithSelector(int x, int y, const char* item, bool isSelected) const;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user