diff --git a/src/buzz/BuzzerFeedbackThread.cpp b/src/buzz/BuzzerFeedbackThread.cpp index 838224c69..ce762c764 100644 --- a/src/buzz/BuzzerFeedbackThread.cpp +++ b/src/buzz/BuzzerFeedbackThread.cpp @@ -28,7 +28,6 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event) case INPUT_BROKER_USER_PRESS: case INPUT_BROKER_ALT_PRESS: case INPUT_BROKER_SELECT: - case INPUT_BROKER_SELECT_LONG: playBeep(); // Confirmation feedback break; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 5e29814cb..fa71e17d8 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -216,44 +216,6 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t ui->update(); } -void Screen::showTextInput(const char *header, const char *initialText, uint32_t durationMs, - std::function textCallback) -{ - LOG_INFO("showTextInput called with header='%s', durationMs=%d", header ? header : "NULL", durationMs); - - if (NotificationRenderer::virtualKeyboard) { - delete NotificationRenderer::virtualKeyboard; - NotificationRenderer::virtualKeyboard = nullptr; - } - - NotificationRenderer::textInputCallback = nullptr; - - NotificationRenderer::virtualKeyboard = new VirtualKeyboard(); - if (header) { - NotificationRenderer::virtualKeyboard->setHeader(header); - } - if (initialText) { - NotificationRenderer::virtualKeyboard->setInputText(initialText); - } - - // Set up callback with safer cleanup mechanism - NotificationRenderer::textInputCallback = textCallback; - NotificationRenderer::virtualKeyboard->setCallback([textCallback](const std::string &text) { textCallback(text); }); - - // Store the message and set the expiration timestamp (use same pattern as other notifications) - strncpy(NotificationRenderer::alertBannerMessage, header ? header : "Text Input", 255); - NotificationRenderer::alertBannerMessage[255] = '\0'; - NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs; - NotificationRenderer::pauseBanner = false; - NotificationRenderer::current_notification_type = notificationTypeEnum::text_input; - - // Set the overlay using the same pattern as other notification types - static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; - ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); - ui->setTargetFPS(60); - ui->update(); -} - static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { uint8_t module_frame; @@ -751,19 +713,13 @@ int32_t Screen::runOnce() handleSetOn(false); break; case Cmd::ON_PRESS: - if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) { - handleOnPress(); - } + handleOnPress(); break; case Cmd::SHOW_PREV_FRAME: - if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) { - handleShowPrevFrame(); - } + handleShowPrevFrame(); break; case Cmd::SHOW_NEXT_FRAME: - if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) { - handleShowNextFrame(); - } + handleShowNextFrame(); break; case Cmd::START_ALERT_FRAME: { showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away @@ -785,9 +741,7 @@ int32_t Screen::runOnce() NotificationRenderer::pauseBanner = false; case Cmd::STOP_BOOT_SCREEN: EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame - if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) { - setFrames(); - } + setFrames(); break; case Cmd::NOOP: break; @@ -823,7 +777,6 @@ int32_t Screen::runOnce() if (showingNormalScreen) { // standard screen loop handling here if (config.display.auto_screen_carousel_secs > 0 && - NotificationRenderer::current_notification_type != notificationTypeEnum::text_input && !Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) { // If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead @@ -914,11 +867,6 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) // Called when a frame should be added / removed, or custom frames should be cleared void Screen::setFrames(FrameFocus focus) { - // Block setFrames calls when virtual keyboard is active to prevent overlay interference - if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) { - return; - } - uint8_t originalPosition = ui->getUiState()->currentFrame; uint8_t previousFrameCount = framesetInfo.frameCount; FramesetInfo fsi; // Location of specific frames, for applying focus parameter @@ -1365,11 +1313,6 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) // Triggered by MeshModules int Screen::handleUIFrameEvent(const UIFrameEvent *event) { - // Block UI frame events when virtual keyboard is active - if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) { - return 0; - } - if (showingNormalScreen) { // Regenerate the frameset, potentially honoring a module's internal requestFocus() call if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET) @@ -1392,16 +1335,6 @@ int Screen::handleInputEvent(const InputEvent *event) if (!screenOn) return 0; - // Handle text input notifications specially - pass input to virtual keyboard - if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) { - NotificationRenderer::inEvent = *event; - static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback}; - ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); - setFastFramerate(); // Draw ASAP - ui->update(); - return 0; - } - #ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw. EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 0f100d455..265900131 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -12,7 +12,7 @@ #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) namespace graphics { -enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker, text_input }; +enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker }; struct BannerOverlayOptions { const char *message; @@ -313,8 +313,6 @@ class Screen : public concurrency::OSThread void showNodePicker(const char *message, uint32_t durationMs, std::function bannerCallback); void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function bannerCallback); - void showTextInput(const char *header, const char *initialText, uint32_t durationMs, - std::function textCallback); void requestMenu(graphics::menuHandler::screenMenus menuToShow) { diff --git a/src/graphics/VirtualKeyboard.cpp b/src/graphics/VirtualKeyboard.cpp deleted file mode 100644 index 84d5551cb..000000000 --- a/src/graphics/VirtualKeyboard.cpp +++ /dev/null @@ -1,738 +0,0 @@ -#include "VirtualKeyboard.h" -#include "configuration.h" -#include "graphics/Screen.h" -#include "graphics/ScreenFonts.h" -#include "graphics/SharedUIDisplay.h" -#include "main.h" -#include -#include - -namespace graphics -{ - -VirtualKeyboard::VirtualKeyboard() : cursorRow(0), cursorCol(0), lastActivityTime(millis()) -{ - initializeKeyboard(); - // Set cursor to H(2, 5) - cursorRow = 2; - cursorCol = 5; -} - -VirtualKeyboard::~VirtualKeyboard() {} - -void VirtualKeyboard::initializeKeyboard() -{ - // New 4 row, 11 column keyboard layout: - static const char LAYOUT[KEYBOARD_ROWS][KEYBOARD_COLS] = {{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b'}, - {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n'}, - {'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', ' '}, - {'z', 'x', 'c', 'v', 'b', 'n', 'm', '.', ',', '?', '\x1b'}}; - - // Derive layout dimensions and assert they match the configured keyboard grid - constexpr int LAYOUT_ROWS = (int)(sizeof(LAYOUT) / sizeof(LAYOUT[0])); - constexpr int LAYOUT_COLS = (int)(sizeof(LAYOUT[0]) / sizeof(LAYOUT[0][0])); - static_assert(LAYOUT_ROWS == KEYBOARD_ROWS, "LAYOUT rows must equal KEYBOARD_ROWS"); - static_assert(LAYOUT_COLS == KEYBOARD_COLS, "LAYOUT cols must equal KEYBOARD_COLS"); - - // Initialize all keys to empty first - for (int row = 0; row < LAYOUT_ROWS; row++) { - for (int col = 0; col < LAYOUT_COLS; col++) { - keyboard[row][col] = {0, VK_CHAR, 0, 0, 0, 0}; - } - } - - // Fill keyboard from the 2D layout - for (int row = 0; row < LAYOUT_ROWS; row++) { - for (int col = 0; col < LAYOUT_COLS; col++) { - char ch = LAYOUT[row][col]; - // No empty slots in the simplified layout - - VirtualKeyType type = VK_CHAR; - if (ch == '\b') { - type = VK_BACKSPACE; - } else if (ch == '\n') { - type = VK_ENTER; - } else if (ch == '\x1b') { // ESC - type = VK_ESC; - } else if (ch == ' ') { - type = VK_SPACE; - } - - // Make action keys wider to fit text while keeping the last column aligned - uint8_t width = (type == VK_BACKSPACE || type == VK_ENTER || type == VK_SPACE) ? (KEY_WIDTH * 3) : KEY_WIDTH; - keyboard[row][col] = {ch, type, (uint8_t)(col * KEY_WIDTH), (uint8_t)(row * KEY_HEIGHT), width, KEY_HEIGHT}; - } - } -} - -void VirtualKeyboard::draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY) -{ - // Repeat ticking is driven by NotificationRenderer once per frame - // Base styles - display->setColor(WHITE); - display->setFont(FONT_SMALL); - - // Screen geometry - const int screenW = display->getWidth(); - const int screenH = display->getHeight(); - - // Decide wide-screen mode: if there is comfortable width, allow taller keys and reserve fixed width for last column labels - // Heuristic: if screen width >= 200px (e.g., 240x135), treat as wide - const bool isWide = screenW >= 200; - - // Determine last-column label max width - display->setFont(FONT_SMALL); - const int wENTER = display->getStringWidth("ENTER"); - int lastColLabelW = wENTER; // ENTER is usually the widest - // Smaller padding on very small screens to avoid excessive whitespace - const int lastColPad = (screenW <= 128 ? 2 : 6); - const int reservedLastColW = lastColLabelW + lastColPad; // reserved width for last column keys - - // Always reserve width for the rightmost text column to avoid overlap on small screens - int cellW = 0; - int leftoverW = 0; - { - const int leftCols = KEYBOARD_COLS - 1; // 10 input characters - int usableW = screenW - reservedLastColW; - if (usableW < leftCols) { - // Guard: ensure at least 1px per left cell if labels are extremely wide (unlikely) - usableW = leftCols; - } - cellW = usableW / leftCols; - leftoverW = usableW - cellW * leftCols; // distribute extra pixels over left columns (left to right) - } - - // Dynamic key geometry - int cellH = KEY_HEIGHT; - int keyboardStartY = 0; - if (screenH <= 64) { - const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL - 2); - const int gapBelowHeader = 0; - const int singleLineBoxHeight = FONT_HEIGHT_SMALL; - const int gapAboveKeyboard = 0; - keyboardStartY = offsetY + headerHeight + gapBelowHeader + singleLineBoxHeight + gapAboveKeyboard; - if (keyboardStartY < 0) - keyboardStartY = 0; - if (keyboardStartY > screenH) - keyboardStartY = screenH; - int keyboardHeight = screenH - keyboardStartY; - cellH = std::max(1, keyboardHeight / KEYBOARD_ROWS); - } else if (isWide) { - // For wide screens (e.g., T114 240x135), prefer square keys: height equals left-column key width. - cellH = std::max((int)KEY_HEIGHT, cellW); - - // Guarantee at least 2 lines of input are visible by reducing cell height minimally if needed. - // Replicate the spacing used in drawInputArea(): headerGap=1, box-to-header gap=1, gap above keyboard=1 - display->setFont(FONT_SMALL); - const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL + 1); - const int headerToBoxGap = 1; - const int gapAboveKb = 1; - const int minBoxHeightForTwoLines = 2 * FONT_HEIGHT_SMALL + 2; // inner 1px top/bottom - int maxKeyboardHeight = screenH - (offsetY + headerHeight + headerToBoxGap + minBoxHeightForTwoLines + gapAboveKb); - int maxCellHAllowed = maxKeyboardHeight / KEYBOARD_ROWS; - if (maxCellHAllowed < (int)KEY_HEIGHT) - maxCellHAllowed = KEY_HEIGHT; - if (maxCellHAllowed > 0 && cellH > maxCellHAllowed) { - cellH = maxCellHAllowed; - } - // Keyboard placement from bottom for wide screens - int keyboardHeight = KEYBOARD_ROWS * cellH; - keyboardStartY = screenH - keyboardHeight; - if (keyboardStartY < 0) - keyboardStartY = 0; - } else { - // Default (non-wide, non-64px) behavior: use key height heuristic and place at bottom - cellH = KEY_HEIGHT; - int keyboardHeight = KEYBOARD_ROWS * cellH; - keyboardStartY = screenH - keyboardHeight; - if (keyboardStartY < 0) - keyboardStartY = 0; - } - - // Draw input area above keyboard - drawInputArea(display, offsetX, offsetY, keyboardStartY); - - // Precompute per-column x and width with leftover distributed over left columns for even spacing - int colX[KEYBOARD_COLS]; - int colW[KEYBOARD_COLS]; - int runningX = offsetX; - for (int col = 0; col < KEYBOARD_COLS - 1; ++col) { - int wcol = cellW + (col < leftoverW ? 1 : 0); - colX[col] = runningX; - colW[col] = wcol; - runningX += wcol; - } - // Last column - colX[KEYBOARD_COLS - 1] = runningX; - colW[KEYBOARD_COLS - 1] = reservedLastColW; - - // Draw keyboard grid - for (int row = 0; row < KEYBOARD_ROWS; row++) { - for (int col = 0; col < KEYBOARD_COLS; col++) { - const VirtualKey &k = keyboard[row][col]; - if (k.character != 0 || k.type != VK_CHAR) { - const bool isLastCol = (col == KEYBOARD_COLS - 1); - int x = colX[col]; - int w = colW[col]; - int y = offsetY + keyboardStartY + row * cellH; - int h = cellH; - bool selected = (row == cursorRow && col == cursorCol); - drawKey(display, k, selected, x, y, (uint8_t)w, (uint8_t)h, isLastCol); - } - } - } -} - -void VirtualKeyboard::drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY) -{ - display->setColor(WHITE); - - const int screenWidth = display->getWidth(); - const int screenHeight = display->getHeight(); - // Use the standard small font metrics for input box sizing (restore original size) - const int inputLineH = FONT_HEIGHT_SMALL; - - // Header uses the standard small (which may be larger on big screens) - display->setFont(FONT_SMALL); - int headerHeight = 0; - if (!headerText.empty()) { - // Draw header and reserve exact font height (plus a tighter gap) to maximize input area - display->drawString(offsetX + 2, offsetY, headerText.c_str()); - if (screenHeight <= 64) { - headerHeight = FONT_HEIGHT_SMALL - 2; // 11px - } else { - headerHeight = FONT_HEIGHT_SMALL; // no extra padding baked in - } - } - - const int boxX = offsetX; - const int boxWidth = screenWidth; - int boxY; - int boxHeight; - if (screenHeight <= 64) { - const int gapBelowHeader = 0; - const int fixedBoxHeight = inputLineH; - const int gapAboveKeyboard = 0; - boxY = offsetY + headerHeight + gapBelowHeader; - boxHeight = fixedBoxHeight; - if (boxY + boxHeight + gapAboveKeyboard > keyboardStartY) { - int over = boxY + boxHeight + gapAboveKeyboard - keyboardStartY; - boxHeight = std::max(1, fixedBoxHeight - over); - } - } else { - const int gapBelowHeader = 1; - int gapAboveKeyboard = 1; - int tmpBoxY = offsetY + headerHeight + gapBelowHeader; - const int minBoxHeight = inputLineH + 2; - int availableH = keyboardStartY - tmpBoxY - gapAboveKeyboard; - if (availableH < minBoxHeight) - availableH = minBoxHeight; - boxY = tmpBoxY; - boxHeight = availableH; - } - - // Draw box border - display->drawRect(boxX, boxY, boxWidth, boxHeight); - - display->setFont(FONT_SMALL); - - // Text rendering: multi-line if space allows (>= 2 lines), else single-line with leading ellipsis - const int textX = boxX + 2; - const int maxTextWidth = boxWidth - 4; - const int maxLines = (boxHeight - 2) / inputLineH; - if (maxLines >= 2) { - // Inner bounds for caret clamping - const int innerLeft = boxX + 1; - const int innerRight = boxX + boxWidth - 2; - const int innerTop = boxY + 1; - const int innerBottom = boxY + boxHeight - 2; - - // Wrap text greedily into lines that fit maxTextWidth - std::vector lines; - { - std::string remaining = inputText; - while (!remaining.empty()) { - int bestLen = 0; - for (int len = 1; len <= (int)remaining.size(); ++len) { - int w = display->getStringWidth(remaining.substr(0, len).c_str()); - if (w <= maxTextWidth) - bestLen = len; - else - break; - } - if (bestLen == 0) { - // At least show one character to make progress - bestLen = 1; - } - lines.emplace_back(remaining.substr(0, bestLen)); - remaining.erase(0, bestLen); - } - } - - const bool scrolledUp = ((int)lines.size() > maxLines); - int caretX = textX; - int caretY = innerTop; - - // Leave a small top gap to render '...' without replacing the first line - const int topInset = 2; - const int lineStep = std::max(1, inputLineH - 1); // slightly tighter than font height - int lineY = innerTop + topInset; - - if (scrolledUp) { - // Draw three small dots centered horizontally, vertically at the midpoint of the gap - // between the inner top and the first line's top baseline. This avoids using a tall glyph. - const int firstLineTop = lineY; // baseline top for the first visible line - const int gapMidY = innerTop + (firstLineTop - innerTop) / 2 + 1; // shift down 1px as requested - const int centerX = boxX + boxWidth / 2; - const int dotSpacing = 3; // px between dots - const int dotSize = 1; // small square dot - display->fillRect(centerX - dotSpacing, gapMidY, dotSize, dotSize); - display->fillRect(centerX, gapMidY, dotSize, dotSize); - display->fillRect(centerX + dotSpacing, gapMidY, dotSize, dotSize); - } - - // How many lines fit with our top inset and tighter step - const int linesCapacity = std::max(1, (innerBottom - lineY + 1) / lineStep); - const int linesToShow = std::min((int)lines.size(), linesCapacity); - const int startIndex = scrolledUp ? ((int)lines.size() - linesToShow) : 0; - - for (int i = 0; i < linesToShow; ++i) { - const std::string &chunk = lines[startIndex + i]; - display->drawString(textX, lineY, chunk.c_str()); - caretX = textX + display->getStringWidth(chunk.c_str()); - caretY = lineY; - lineY += lineStep; - } - - // Draw caret at end of the last visible line - int caretPadY = 2; - if (boxHeight >= inputLineH + 4) - caretPadY = 3; - int cursorTop = caretY + caretPadY; - // Use lineStep so caret height matches the row spacing - int cursorH = lineStep - caretPadY * 2; - if (cursorH < 1) - cursorH = 1; - // Clamp vertical bounds to stay inside the inner rect - if (cursorTop < innerTop) - cursorTop = innerTop; - if (cursorTop + cursorH - 1 > innerBottom) - cursorH = innerBottom - cursorTop + 1; - if (cursorH < 1) - cursorH = 1; - // Only draw if cursor is inside inner bounds - if (caretX >= innerLeft && caretX <= innerRight) { - display->drawVerticalLine(caretX, cursorTop, cursorH); - } - } else { - std::string displayText = inputText; - int textW = display->getStringWidth(displayText.c_str()); - std::string scrolled = displayText; - if (textW > maxTextWidth) { - // Trim from the left until it fits - while (textW > maxTextWidth && !scrolled.empty()) { - scrolled.erase(0, 1); - textW = display->getStringWidth(scrolled.c_str()); - } - // Add leading ellipsis and ensure it still fits - if (scrolled != displayText) { - scrolled = "..." + scrolled; - textW = display->getStringWidth(scrolled.c_str()); - // If adding ellipsis causes overflow, trim more after the ellipsis - while (textW > maxTextWidth && scrolled.size() > 3) { - scrolled.erase(3, 1); // remove chars after the ellipsis - textW = display->getStringWidth(scrolled.c_str()); - } - } - } else { - // Keep textW in sync with what we draw - textW = display->getStringWidth(scrolled.c_str()); - } - - int textY; - if (screenHeight <= 64) { - textY = boxY + (boxHeight - inputLineH) / 2; - } else { - const int innerLeft = boxX + 1; - const int innerRight = boxX + boxWidth - 2; - const int innerTop = boxY + 1; - const int innerBottom = boxY + boxHeight - 2; - - // Center text vertically within inner box for single-line, then clamp so it never overlaps borders - int innerH = innerBottom - innerTop + 1; - textY = innerTop + std::max(0, (innerH - inputLineH) / 2); - // Clamp fully inside the inner rect - if (textY < innerTop) - textY = innerTop; - int maxTop = innerBottom - inputLineH + 1; - if (textY > maxTop) - textY = maxTop; - } - - if (!scrolled.empty()) { - display->drawString(textX, textY, scrolled.c_str()); - } - - int cursorX = textX + textW; - if (screenHeight > 64) { - const int innerRight = boxX + boxWidth - 2; - if (cursorX > innerRight) - cursorX = innerRight; - } - - int cursorTop, cursorH; - if (screenHeight <= 64) { - cursorH = 10; - cursorTop = boxY + (boxHeight - cursorH) / 2; - } else { - const int innerLeft = boxX + 1; - const int innerRight = boxX + boxWidth - 2; - const int innerTop = boxY + 1; - const int innerBottom = boxY + boxHeight - 2; - - cursorTop = boxY + 2; - cursorH = boxHeight - 4; - if (cursorH < 1) - cursorH = 1; - if (cursorTop < innerTop) - cursorTop = innerTop; - if (cursorTop + cursorH - 1 > innerBottom) - cursorH = innerBottom - cursorTop + 1; - if (cursorH < 1) - cursorH = 1; - - if (cursorX < innerLeft || cursorX > innerRight) - return; - } - - display->drawVerticalLine(cursorX, cursorTop, cursorH); - } -} - -void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t width, - uint8_t height, bool isLastCol) -{ - // Draw key content - display->setFont(FONT_SMALL); - const int fontH = FONT_HEIGHT_SMALL; - // Build label and metrics first - std::string keyText; - if (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC) { - // Keep literal text labels for the action keys on the rightmost column - keyText = (key.type == VK_BACKSPACE) ? "BACK" - : (key.type == VK_ENTER) ? "ENTER" - : (key.type == VK_SPACE) ? "SPACE" - : (key.type == VK_ESC) ? "ESC" - : ""; - } else { - char c = getCharForKey(key, false); - if (c >= 'a' && c <= 'z') { - c = c - 'a' + 'A'; - } - keyText = (key.character == ' ' || key.character == '_') ? "_" : std::string(1, c); - } - - int textWidth = display->getStringWidth(keyText.c_str()); - // Label alignment - // - Rightmost action column: right-align text with a small right padding (~2px) so it hugs screen edge neatly. - // - Other keys: center horizontally; use ceil-style rounding to avoid appearing left-biased on odd widths. - int textX; - if (isLastCol) { - const int rightPad = 1; - textX = x + width - textWidth - rightPad; - if (textX < x) - textX = x; // guard - } else { - if (display->getHeight() <= 64 && (key.character >= '0' && key.character <= '9')) { - textX = x + (width - textWidth + 1) / 2; - } else { - textX = x + (width - textWidth) / 2; - } - } - int contentTop = y; - int contentH = height; - if (selected) { - display->setColor(WHITE); - bool isAction = (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC); - - if (display->getHeight() <= 64 && !isAction) { - display->fillRect(x, y, width, height); - } else if (isAction) { - const int padX = 1; - const int padY = 2; - int hlW = textWidth + padX * 2; - int hlX = textX - padX; - - if (hlX < x) { - hlW -= (x - hlX); - hlX = x; - } - int maxW = (x + width) - hlX; - if (hlW > maxW) - hlW = maxW; - if (hlW < 1) - hlW = 1; - - int hlH = std::min(fontH + padY * 2, (int)height); - int hlY = y + (height - hlH) / 2; - display->fillRect(hlX, hlY, hlW, hlH); - contentTop = hlY; - contentH = hlH; - } else { - display->fillRect(x, y, width, height); - } - display->setColor(BLACK); - } else { - display->setColor(WHITE); - } - - int centeredTextY; - if (display->getHeight() <= 64) { - centeredTextY = y + (height - fontH) / 2; - } else { - centeredTextY = contentTop + (contentH - fontH) / 2; - } - if (display->getHeight() > 64) { - if (centeredTextY < contentTop) - centeredTextY = contentTop; - if (centeredTextY + fontH > contentTop + contentH) - centeredTextY = std::max(contentTop, contentTop + contentH - fontH); - } - - if (display->getHeight() <= 64 && keyText.size() == 1) { - char ch = keyText[0]; - if (ch == '.' || ch == ',' || ch == ';') { - centeredTextY -= 1; - } - } - display->drawString(textX, centeredTextY, keyText.c_str()); -} - -char VirtualKeyboard::getCharForKey(const VirtualKey &key, bool isLongPress) -{ - if (key.type != VK_CHAR) { - return key.character; - } - - char c = key.character; - - // Long-press: only keep letter lowercase->uppercase conversion; remove other symbol mappings - if (isLongPress && c >= 'a' && c <= 'z') { - c = (char)(c - 'a' + 'A'); - } - - return c; -} - -void VirtualKeyboard::moveCursorDelta(int dRow, int dCol) -{ - resetTimeout(); - // wrap around rows and cols in the 4x11 grid - int r = (int)cursorRow + dRow; - int c = (int)cursorCol + dCol; - if (r < 0) - r = KEYBOARD_ROWS - 1; - else if (r >= KEYBOARD_ROWS) - r = 0; - if (c < 0) - c = KEYBOARD_COLS - 1; - else if (c >= KEYBOARD_COLS) - c = 0; - cursorRow = (uint8_t)r; - cursorCol = (uint8_t)c; -} - -void VirtualKeyboard::moveCursorUp() -{ - moveCursorDelta(-1, 0); -} -void VirtualKeyboard::moveCursorDown() -{ - moveCursorDelta(1, 0); -} -void VirtualKeyboard::moveCursorLeft() -{ - resetTimeout(); - - if (cursorCol > 0) { - cursorCol--; - } else { - if (cursorRow > 0) { - cursorRow--; - cursorCol = KEYBOARD_COLS - 1; - } else { - cursorRow = KEYBOARD_ROWS - 1; - cursorCol = KEYBOARD_COLS - 1; - } - } -} -void VirtualKeyboard::moveCursorRight() -{ - resetTimeout(); - - if (cursorCol < KEYBOARD_COLS - 1) { - cursorCol++; - } else { - if (cursorRow < KEYBOARD_ROWS - 1) { - cursorRow++; - cursorCol = 0; - } else { - cursorRow = 0; - cursorCol = 0; - } - } -} - -void VirtualKeyboard::handlePress() -{ - resetTimeout(); // Reset timeout on any input activity - - const VirtualKey &key = keyboard[cursorRow][cursorCol]; - - // Don't handle press if the key is empty (but allow special keys) - if (key.character == 0 && key.type == VK_CHAR) { - return; - } - - // For character keys, insert lowercase character - if (key.type == VK_CHAR) { - insertCharacter(getCharForKey(key, false)); // false = lowercase/normal char - return; - } - - // Handle non-character keys immediately - switch (key.type) { - case VK_BACKSPACE: - deleteCharacter(); - break; - case VK_ENTER: - submitText(); - break; - case VK_SPACE: - insertCharacter(' '); - break; - case VK_ESC: - if (onTextEntered) { - std::function callback = onTextEntered; - onTextEntered = nullptr; - inputText = ""; - callback(""); - } - return; - default: - break; - } -} - -void VirtualKeyboard::handleLongPress() -{ - resetTimeout(); // Reset timeout on any input activity - - const VirtualKey &key = keyboard[cursorRow][cursorCol]; - - // Don't handle press if the key is empty (but allow special keys) - if (key.character == 0 && key.type == VK_CHAR) { - return; - } - - // For character keys, insert uppercase/alternate character - if (key.type == VK_CHAR) { - insertCharacter(getCharForKey(key, true)); // true = uppercase/alternate char - return; - } - - switch (key.type) { - case VK_BACKSPACE: - // One-shot: delete up to 5 characters on long press - for (int i = 0; i < 5; ++i) { - if (inputText.empty()) - break; - deleteCharacter(); - } - break; - case VK_ENTER: - submitText(); - break; - case VK_SPACE: - insertCharacter(' '); - break; - case VK_ESC: - if (onTextEntered) { - onTextEntered(""); - } - break; - default: - break; - } -} - -void VirtualKeyboard::insertCharacter(char c) -{ - if (inputText.length() < 160) { // Reasonable text length limit - inputText += c; - } -} - -void VirtualKeyboard::deleteCharacter() -{ - if (!inputText.empty()) { - inputText.pop_back(); - } -} - -void VirtualKeyboard::submitText() -{ - LOG_INFO("Virtual keyboard: submitting text '%s'", inputText.c_str()); - - // Only submit if text is not empty - if (!inputText.empty() && onTextEntered) { - // Store callback and text to submit before clearing callback - std::function callback = onTextEntered; - std::string textToSubmit = inputText; - onTextEntered = nullptr; - // Don't clear inputText here - let the calling module handle cleanup - // inputText = ""; // Removed: keep text visible until module cleans up - callback(textToSubmit); - } else if (inputText.empty()) { - // For empty text, just ignore the submission - don't clear callback - // This keeps the virtual keyboard responsive for further input - LOG_INFO("Virtual keyboard: empty text submitted, ignoring - keyboard remains active"); - } else { - // No callback available - if (screen) { - screen->setFrames(graphics::Screen::FOCUS_PRESERVE); - } - } -} - -void VirtualKeyboard::setInputText(const std::string &text) -{ - inputText = text; -} - -std::string VirtualKeyboard::getInputText() const -{ - return inputText; -} - -void VirtualKeyboard::setHeader(const std::string &header) -{ - headerText = header; -} - -void VirtualKeyboard::setCallback(std::function callback) -{ - onTextEntered = callback; -} - -void VirtualKeyboard::resetTimeout() -{ - lastActivityTime = millis(); -} - -bool VirtualKeyboard::isTimedOut() const -{ - return (millis() - lastActivityTime) > TIMEOUT_MS; -} - -} // namespace graphics diff --git a/src/graphics/VirtualKeyboard.h b/src/graphics/VirtualKeyboard.h deleted file mode 100644 index 169163b57..000000000 --- a/src/graphics/VirtualKeyboard.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include "configuration.h" -#include -#include -#include - -namespace graphics -{ - -enum VirtualKeyType { VK_CHAR, VK_BACKSPACE, VK_ENTER, VK_SHIFT, VK_ESC, VK_SPACE }; - -struct VirtualKey { - char character; - VirtualKeyType type; - uint8_t x; - uint8_t y; - uint8_t width; - uint8_t height; -}; - -class VirtualKeyboard -{ - public: - VirtualKeyboard(); - ~VirtualKeyboard(); - - void draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY); - void setInputText(const std::string &text); - std::string getInputText() const; - void setHeader(const std::string &header); - void setCallback(std::function callback); - - // Navigation methods for encoder input - void moveCursorUp(); - void moveCursorDown(); - void moveCursorLeft(); - void moveCursorRight(); - void handlePress(); - void handleLongPress(); - - // Timeout management - void resetTimeout(); - bool isTimedOut() const; - - private: - static const uint8_t KEYBOARD_ROWS = 4; - static const uint8_t KEYBOARD_COLS = 11; - static const uint8_t KEY_WIDTH = 9; - static const uint8_t KEY_HEIGHT = 9; // Compressed to fit 4 rows on 64px displays - static const uint8_t KEYBOARD_START_Y = 26; // Start just below input box bottom - - VirtualKey keyboard[KEYBOARD_ROWS][KEYBOARD_COLS]; - - std::string inputText; - std::string headerText; - std::function onTextEntered; - - uint8_t cursorRow; - uint8_t cursorCol; - - // Timeout management for auto-exit - uint32_t lastActivityTime; - static const uint32_t TIMEOUT_MS = 60000; // 1 minute timeout - - void initializeKeyboard(); - void drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t w, uint8_t h, - bool isLastCol); - void drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY); - - // Unified cursor movement helper - void moveCursorDelta(int dRow, int dCol); - - char getCharForKey(const VirtualKey &key, bool isLongPress = false); - void insertCharacter(char c); - void deleteCharacter(); - void submitText(); -}; - -} // namespace graphics diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index e02948864..512f650ec 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -10,10 +10,7 @@ #include "graphics/Screen.h" #include "graphics/SharedUIDisplay.h" #include "graphics/draw/UIRenderer.h" -#include "input/RotaryEncoderInterruptImpl1.h" -#include "input/UpDownInterruptImpl1.h" #include "main.h" -#include "mesh/MeshTypes.h" #include "modules/AdminModule.h" #include "modules/CannedMessageModule.h" #include "modules/KeyVerificationModule.h" diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index 221d95075..3d635e588 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -38,8 +38,6 @@ bool NotificationRenderer::pauseBanner = false; notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none; uint32_t NotificationRenderer::numDigits = 0; uint32_t NotificationRenderer::currentNumber = 0; -VirtualKeyboard *NotificationRenderer::virtualKeyboard = nullptr; -std::function NotificationRenderer::textInputCallback = nullptr; uint32_t pow_of_10(uint32_t n) { @@ -91,33 +89,14 @@ void NotificationRenderer::resetBanner() void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state) { - // Handle text_input notifications first - they have their own timeout/banner logic - if (current_notification_type == notificationTypeEnum::text_input) { - // Check for timeout and reset if needed for text input - if (millis() > alertBannerUntil && alertBannerUntil > 0) { - resetBanner(); - return; - } - drawTextInput(display, state); - return; - } - - if (millis() > alertBannerUntil && alertBannerUntil > 0) { + if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0') resetBanner(); - } - - // Exit if no banner is showing or banner is paused - if (!isOverlayBannerShowing() || pauseBanner) { + if (!isOverlayBannerShowing() || pauseBanner) return; - } - switch (current_notification_type) { case notificationTypeEnum::none: // Do nothing - no notification to display break; - case notificationTypeEnum::text_input: - // Already handled above with dedicated logic (early return). Keep a case here to satisfy -Wswitch. - break; case notificationTypeEnum::text_banner: case notificationTypeEnum::selection_picker: drawAlertBannerOverlay(display, state); @@ -596,90 +575,6 @@ void NotificationRenderer::drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUi "Please be patient and do not power off."); } -void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state) -{ - if (virtualKeyboard) { - // Check for timeout and auto-exit if needed - if (virtualKeyboard->isTimedOut()) { - LOG_INFO("Virtual keyboard timeout - auto-exiting"); - // Cancel virtual keyboard - call callback with empty string to indicate timeout - auto callback = textInputCallback; // Store callback before clearing - - // Clean up first to prevent re-entry - delete virtualKeyboard; - virtualKeyboard = nullptr; - textInputCallback = nullptr; - resetBanner(); - - // Call callback after cleanup - if (callback) { - callback(""); - } - - // Restore normal overlays - if (screen) { - screen->setFrames(graphics::Screen::FOCUS_PRESERVE); - } - return; - } - - // Handle input events for virtual keyboard navigation - if (inEvent.inputEvent != INPUT_BROKER_NONE) { - if (inEvent.inputEvent == INPUT_BROKER_UP) { - virtualKeyboard->moveCursorUp(); - } else if (inEvent.inputEvent == INPUT_BROKER_DOWN) { - virtualKeyboard->moveCursorDown(); - } else if (inEvent.inputEvent == INPUT_BROKER_LEFT) { - virtualKeyboard->moveCursorLeft(); - } else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) { - virtualKeyboard->moveCursorRight(); - } else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { - // Long press UP = move left - virtualKeyboard->moveCursorLeft(); - } else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { - // Long press DOWN = move right - virtualKeyboard->moveCursorRight(); - } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { - virtualKeyboard->handlePress(); - } else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) { - virtualKeyboard->handleLongPress(); - } else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) { - // Cancel virtual keyboard - call callback with empty string - auto callback = textInputCallback; // Store callback before clearing - - // Clean up first to prevent re-entry - delete virtualKeyboard; - virtualKeyboard = nullptr; - textInputCallback = nullptr; - resetBanner(); - - // Call callback after cleanup - if (callback) { - callback(""); - } - - // Restore normal overlays - if (screen) { - screen->setFrames(graphics::Screen::FOCUS_PRESERVE); - } - return; - } - - // Reset input event after processing - inEvent.inputEvent = INPUT_BROKER_NONE; - } - - // Clear the display and draw virtual keyboard - display->setColor(BLACK); - display->fillRect(0, 0, display->getWidth(), display->getHeight()); - display->setColor(WHITE); - virtualKeyboard->draw(display, 0, 0); - } else { - // If virtualKeyboard is null, reset the banner to avoid getting stuck - resetBanner(); - } -} - bool NotificationRenderer::isOverlayBannerShowing() { return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil); diff --git a/src/graphics/draw/NotificationRenderer.h b/src/graphics/draw/NotificationRenderer.h index edb069513..9c30b329c 100644 --- a/src/graphics/draw/NotificationRenderer.h +++ b/src/graphics/draw/NotificationRenderer.h @@ -3,9 +3,6 @@ #include "OLEDDisplay.h" #include "OLEDDisplayUi.h" #include "graphics/Screen.h" -#include "graphics/VirtualKeyboard.h" -#include -#include #define MAX_LINES 5 namespace graphics @@ -25,8 +22,6 @@ class NotificationRenderer static std::function alertBannerCallback; static uint32_t numDigits; static uint32_t currentNumber; - static VirtualKeyboard *virtualKeyboard; - static std::function textInputCallback; static bool pauseBanner; @@ -35,7 +30,6 @@ class NotificationRenderer static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state); static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state); static void drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state); - static void drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state); static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1], uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0); diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 012a403f5..4487fa662 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -4,7 +4,6 @@ enum input_broker_event { INPUT_BROKER_NONE = 0, INPUT_BROKER_SELECT = 10, - INPUT_BROKER_SELECT_LONG, INPUT_BROKER_UP = 17, INPUT_BROKER_DOWN = 18, INPUT_BROKER_LEFT = 19, diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp index 4c8ce6409..d41ad2fd6 100644 --- a/src/input/TrackballInterruptBase.cpp +++ b/src/input/TrackballInterruptBase.cpp @@ -1,14 +1,12 @@ #include "TrackballInterruptBase.h" #include "configuration.h" -extern bool osk_found; TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {} void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp, input_broker_event eventLeft, - input_broker_event eventRight, input_broker_event eventPressed, - input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(), - void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)()) + input_broker_event eventRight, input_broker_event eventPressed, void (*onIntDown)(), + void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)()) { this->_pinDown = pinDown; this->_pinUp = pinUp; @@ -20,7 +18,6 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef this->_eventLeft = eventLeft; this->_eventRight = eventRight; this->_eventPressed = eventPressed; - this->_eventPressedLong = eventPressedLong; if (pinPress != 255) { pinMode(pinPress, INPUT_PULLUP); @@ -43,9 +40,9 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef attachInterrupt(this->_pinRight, onIntRight, TB_DIRECTION); } - LOG_DEBUG("Trackball GPIO initialized - UP:%d DOWN:%d LEFT:%d RIGHT:%d PRESS:%d", this->_pinUp, this->_pinDown, - this->_pinLeft, this->_pinRight, pinPress); - osk_found = true; + LOG_DEBUG("Trackball GPIO initialized (%d, %d, %d, %d, %d)", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight, + pinPress); + this->setInterval(100); } @@ -53,47 +50,10 @@ int32_t TrackballInterruptBase::runOnce() { InputEvent e; e.inputEvent = INPUT_BROKER_NONE; - - // Handle long press detection for press button - if (pressDetected && pressStartTime > 0) { - uint32_t pressDuration = millis() - pressStartTime; - bool buttonStillPressed = false; - -#if defined(T_DECK) - buttonStillPressed = (this->action == TB_ACTION_PRESSED); -#else - buttonStillPressed = !digitalRead(_pinPress); -#endif - - if (!buttonStillPressed) { - // Button released - if (pressDuration < LONG_PRESS_DURATION) { - // Short press - e.inputEvent = this->_eventPressed; - } - // Reset state - pressDetected = false; - pressStartTime = 0; - lastLongPressEventTime = 0; - this->action = TB_ACTION_NONE; - } else if (pressDuration >= LONG_PRESS_DURATION) { - // Long press detected - uint32_t currentTime = millis(); - // Only trigger long press event if enough time has passed since the last one - if (lastLongPressEventTime == 0 || (currentTime - lastLongPressEventTime) >= LONG_PRESS_REPEAT_INTERVAL) { - e.inputEvent = this->_eventPressedLong; - lastLongPressEventTime = currentTime; - } - this->action = TB_ACTION_PRESSED_LONG; - } - } - #if defined(T_DECK) // T-deck gets a super-simple debounce on trackball - if (this->action == TB_ACTION_PRESSED && !pressDetected) { - // Start long press detection - pressDetected = true; - pressStartTime = millis(); - // Don't send event yet, wait to see if it's a long press + if (this->action == TB_ACTION_PRESSED) { + // LOG_DEBUG("Trackball event Press"); + e.inputEvent = this->_eventPressed; } else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) { // LOG_DEBUG("Trackball event UP"); e.inputEvent = this->_eventUp; @@ -108,11 +68,9 @@ int32_t TrackballInterruptBase::runOnce() e.inputEvent = this->_eventRight; } #else - if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress) && !pressDetected) { - // Start long press detection - pressDetected = true; - pressStartTime = millis(); - // Don't send event yet, wait to see if it's a long press + if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress)) { + // LOG_DEBUG("Trackball event Press"); + e.inputEvent = this->_eventPressed; } else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp)) { // LOG_DEBUG("Trackball event UP"); e.inputEvent = this->_eventUp; @@ -133,16 +91,10 @@ int32_t TrackballInterruptBase::runOnce() e.kbchar = 0x00; this->notifyObservers(&e); } + lastEvent = action; + this->action = TB_ACTION_NONE; - // Only update lastEvent for non-press actions or completed press actions - if (this->action != TB_ACTION_PRESSED || !pressDetected) { - lastEvent = action; - if (!pressDetected) { - this->action = TB_ACTION_NONE; - } - } - - return 50; // Check more frequently for better long press detection + return 100; } void TrackballInterruptBase::intPressHandler() diff --git a/src/input/TrackballInterruptBase.h b/src/input/TrackballInterruptBase.h index 38be22f20..92db8720e 100644 --- a/src/input/TrackballInterruptBase.h +++ b/src/input/TrackballInterruptBase.h @@ -18,8 +18,8 @@ class TrackballInterruptBase : public Observable, public con explicit TrackballInterruptBase(const char *name); void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp, input_broker_event eventLeft, input_broker_event eventRight, - input_broker_event eventPressed, input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(), - void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)()); + input_broker_event eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(), + void (*onIntPress)()); void intPressHandler(); void intDownHandler(); void intUpHandler(); @@ -33,7 +33,6 @@ class TrackballInterruptBase : public Observable, public con enum TrackballInterruptBaseActionType { TB_ACTION_NONE, TB_ACTION_PRESSED, - TB_ACTION_PRESSED_LONG, TB_ACTION_UP, TB_ACTION_DOWN, TB_ACTION_LEFT, @@ -47,20 +46,12 @@ class TrackballInterruptBase : public Observable, public con volatile TrackballInterruptBaseActionType action = TB_ACTION_NONE; - // Long press detection for press button - uint32_t pressStartTime = 0; - bool pressDetected = false; - uint32_t lastLongPressEventTime = 0; - static const uint32_t LONG_PRESS_DURATION = 500; // ms - static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 500; // ms - interval between repeated long press events - private: input_broker_event _eventDown = INPUT_BROKER_NONE; input_broker_event _eventUp = INPUT_BROKER_NONE; input_broker_event _eventLeft = INPUT_BROKER_NONE; input_broker_event _eventRight = INPUT_BROKER_NONE; input_broker_event _eventPressed = INPUT_BROKER_NONE; - input_broker_event _eventPressedLong = INPUT_BROKER_NONE; const char *_originName; TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE; }; diff --git a/src/input/TrackballInterruptImpl1.cpp b/src/input/TrackballInterruptImpl1.cpp index 594facdeb..896238f38 100644 --- a/src/input/TrackballInterruptImpl1.cpp +++ b/src/input/TrackballInterruptImpl1.cpp @@ -13,12 +13,11 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe input_broker_event eventLeft = INPUT_BROKER_LEFT; input_broker_event eventRight = INPUT_BROKER_RIGHT; input_broker_event eventPressed = INPUT_BROKER_SELECT; - input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG; TrackballInterruptBase::init(pinDown, pinUp, pinLeft, pinRight, pinPress, eventDown, eventUp, eventLeft, eventRight, - eventPressed, eventPressedLong, TrackballInterruptImpl1::handleIntDown, - TrackballInterruptImpl1::handleIntUp, TrackballInterruptImpl1::handleIntLeft, - TrackballInterruptImpl1::handleIntRight, TrackballInterruptImpl1::handleIntPressed); + eventPressed, TrackballInterruptImpl1::handleIntDown, TrackballInterruptImpl1::handleIntUp, + TrackballInterruptImpl1::handleIntLeft, TrackballInterruptImpl1::handleIntRight, + TrackballInterruptImpl1::handleIntPressed); inputBroker->registerSource(this); } diff --git a/src/main.cpp b/src/main.cpp index 23ffa6b6d..ef5f5a721 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -191,8 +191,6 @@ ScanI2C::DeviceAddress cardkb_found = ScanI2C::ADDRESS_NONE; uint8_t kb_model; // global bool to record that a kb is present bool kb_found = false; -// global bool to record that on-screen keyboard (OSK) is present -bool osk_found = false; // The I2C address of the RTC Module (if found) ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE; @@ -1414,10 +1412,6 @@ void setup() #endif #endif -#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2) - osk_found = true; -#endif - #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER // Start web server thread. webServerThread = new WebServerThread(); diff --git a/src/main.h b/src/main.h index 2ddd4862f..3568daad2 100644 --- a/src/main.h +++ b/src/main.h @@ -32,7 +32,6 @@ extern ScanI2C::DeviceAddress screen_found; extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; extern bool kb_found; -extern bool osk_found; extern ScanI2C::DeviceAddress rtc_found; extern ScanI2C::DeviceAddress accelerometer_found; extern ScanI2C::FoundDevice rgb_found; diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 76b950322..d40dcd24f 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -13,16 +13,12 @@ #include "detect/ScanI2C.h" #include "graphics/Screen.h" #include "graphics/SharedUIDisplay.h" -#include "graphics/draw/NotificationRenderer.h" #include "graphics/emotes.h" #include "graphics/images.h" #include "main.h" // for cardkb_found #include "mesh/generated/meshtastic/cannedmessages.pb.h" #include "modules/AdminModule.h" #include "modules/ExternalNotificationModule.h" // for buzzer control -#if HAS_TRACKBALL -#include "input/TrackballInterruptImpl1.h" -#endif #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" #endif @@ -42,7 +38,6 @@ extern ScanI2C::DeviceAddress cardkb_found; extern bool graphics::isMuted; -extern bool osk_found; static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto"; static NodeNum lastDest = NODENUM_BROADCAST; @@ -156,13 +151,10 @@ int CannedMessageModule::splitConfiguredMessages() int tempCount = 0; // Insert at position 0 (top) tempMessages[tempCount++] = "[Select Destination]"; + #if defined(USE_VIRTUAL_KEYBOARD) - // Add a "Free Text" entry at the top if using a touch screen virtual keyboard + // Add a "Free Text" entry at the top if using a keyboard tempMessages[tempCount++] = "[-- Free Text --]"; -#else - if (osk_found && screen) { - tempMessages[tempCount++] = "[-- Free Text --]"; - } #endif // First message always starts at buffer start @@ -349,8 +341,6 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) case CANNED_MESSAGE_RUN_STATE_FREETEXT: return handleFreeTextInput(event); // All allowed input for this state - // Virtual keyboard mode: Show virtual keyboard and handle input - // If sending, block all input except global/system (handled above) case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE: return 1; @@ -637,56 +627,6 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo notifyObservers(&e); return true; } -#else - if (strcmp(current, "[-- Free Text --]") == 0) { - if (osk_found && screen) { - char headerBuffer[64]; - if (this->dest == NODENUM_BROADCAST) { - snprintf(headerBuffer, sizeof(headerBuffer), "To: Broadcast@%s", channels.getName(this->channel)); - } else { - snprintf(headerBuffer, sizeof(headerBuffer), "To: %s", getNodeName(this->dest)); - } - screen->showTextInput(headerBuffer, "", 300000, [this](const std::string &text) { - if (!text.empty()) { - this->freetext = text.c_str(); - this->payload = CANNED_MESSAGE_RUN_STATE_FREETEXT; - runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE; - currentMessageIndex = -1; - - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; - this->notifyObservers(&e); - screen->forceDisplay(); - - setIntervalFromNow(500); - return; - } else { - // Don't delete virtual keyboard immediately - it might still be executing - // Instead, just clear the callback and reset banner to stop input processing - graphics::NotificationRenderer::textInputCallback = nullptr; - graphics::NotificationRenderer::resetBanner(); - - // Return to inactive state - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - this->currentMessageIndex = -1; - this->freetext = ""; - this->cursor = 0; - - // Force display update to show normal screen - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; - this->notifyObservers(&e); - screen->forceDisplay(); - - // Schedule cleanup for next loop iteration to ensure safe deletion - setIntervalFromNow(50); - return; - } - }); - - return true; - } - } #endif // Normal canned message selection @@ -1003,54 +943,12 @@ int32_t CannedMessageModule::runOnce() // Normal module disable/idle handling if ((this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED) || (this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE)) { - // Clean up virtual keyboard if needed when going inactive - if (graphics::NotificationRenderer::virtualKeyboard && graphics::NotificationRenderer::textInputCallback == nullptr) { - LOG_INFO("Performing delayed virtual keyboard cleanup"); - delete graphics::NotificationRenderer::virtualKeyboard; - graphics::NotificationRenderer::virtualKeyboard = nullptr; - } - temporaryMessage = ""; return INT32_MAX; } - // Handle delayed virtual keyboard message sending - if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) { - // Virtual keyboard message sending case - text was not empty - if (this->freetext.length() > 0) { - LOG_INFO("Processing delayed virtual keyboard send: '%s'", this->freetext.c_str()); - sendText(this->dest, this->channel, this->freetext.c_str(), true); - - // Clean up virtual keyboard after sending - if (graphics::NotificationRenderer::virtualKeyboard) { - LOG_INFO("Cleaning up virtual keyboard after message send"); - delete graphics::NotificationRenderer::virtualKeyboard; - graphics::NotificationRenderer::virtualKeyboard = nullptr; - graphics::NotificationRenderer::textInputCallback = nullptr; - graphics::NotificationRenderer::resetBanner(); - } - - // Clear payload to indicate virtual keyboard processing is complete - // But keep SENDING_ACTIVE state to show "Sending..." screen for 2 seconds - this->payload = 0; - } else { - // Empty message, just go inactive - LOG_INFO("Empty freetext detected in delayed processing, returning to inactive state"); - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - } - - UIFrameEvent e; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; - this->currentMessageIndex = -1; - this->freetext = ""; - this->cursor = 0; - this->notifyObservers(&e); - return 2000; - } - UIFrameEvent e; - if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload != 0 && - this->payload != CANNED_MESSAGE_RUN_STATE_FREETEXT) || + if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION)) { this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; @@ -1060,18 +958,6 @@ int32_t CannedMessageModule::runOnce() this->freetext = ""; this->cursor = 0; this->notifyObservers(&e); - } - // Handle SENDING_ACTIVE state transition after virtual keyboard message - else if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload == 0) { - // This happens after virtual keyboard message sending is complete - LOG_INFO("Virtual keyboard message sending completed, returning to inactive state"); - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - temporaryMessage = ""; - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; - this->currentMessageIndex = -1; - this->freetext = ""; - this->cursor = 0; - this->notifyObservers(&e); } else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) && !Throttle::isWithinTimespanMs(this->lastTouchMillis, INACTIVATE_AFTER_MS)) { // Reset module on inactivity @@ -1080,23 +966,9 @@ int32_t CannedMessageModule::runOnce() this->freetext = ""; this->cursor = 0; this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - - // Clean up virtual keyboard if it exists during timeout - if (graphics::NotificationRenderer::virtualKeyboard) { - LOG_INFO("Cleaning up virtual keyboard due to module timeout"); - delete graphics::NotificationRenderer::virtualKeyboard; - graphics::NotificationRenderer::virtualKeyboard = nullptr; - graphics::NotificationRenderer::textInputCallback = nullptr; - graphics::NotificationRenderer::resetBanner(); - } - this->notifyObservers(&e); } else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) { - if (this->payload == 0) { - // [Exit] button pressed - return to inactive state - LOG_INFO("Processing [Exit] action - returning to inactive state"); - this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - } else if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) { + if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) { if (this->freetext.length() > 0) { sendText(this->dest, this->channel, this->freetext.c_str(), true); this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;