diff --git a/radio/src/thirdparty/libopenui/src/CMakeLists.txt b/radio/src/thirdparty/libopenui/src/CMakeLists.txt index 98bcf5f78..c1a9671d3 100644 --- a/radio/src/thirdparty/libopenui/src/CMakeLists.txt +++ b/radio/src/thirdparty/libopenui/src/CMakeLists.txt @@ -42,5 +42,6 @@ if(SOFTWARE_KEYBOARD) ${LIBOPENUI_SRC} keyboard_text.cpp keyboard_number.cpp + keyboard_base.cpp ) endif() diff --git a/radio/src/thirdparty/libopenui/src/keyboard_base.cpp b/radio/src/thirdparty/libopenui/src/keyboard_base.cpp new file mode 100644 index 000000000..e8444eda2 --- /dev/null +++ b/radio/src/thirdparty/libopenui/src/keyboard_base.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) EdgeTX + * + * Source: + * https://github.com/edgetx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +#include "keyboard_base.h" + +void Keyboard::hide() +{ + if (activeKeyboard) { + activeKeyboard->clearField(); + activeKeyboard = nullptr; + } +} + +void Keyboard::clearField() +{ + detach(); + if (fields) { + fields->setHeight(oldHeight); + fields = nullptr; + } + if (fieldContainer) { + fieldContainer->setHeight(LCD_H - 0 - fieldContainer->top()); + fieldContainer = nullptr; + } + if (field) { + field->setEditMode(false); + field = nullptr; + } +} + +coord_t calcScrollOffsetForField(FormField *newField, Window *topWindow) +{ + // now we need to calculate the offset of the field in the fields scroll container + // start with the current fields top and walk the hierarchy to calculate positon + coord_t offsetY = newField->top(); + + Window* parentWindow = newField->getParent(); + while (parentWindow && parentWindow != topWindow) { + offsetY += parentWindow->top(); + parentWindow = parentWindow->getParent(); + } + + // try and place it in the middle of the screen. The containing window MUST have + // already been resized + return offsetY - topWindow->height() / 2; +} + +void Keyboard::attachKeyboard() +{ + if (activeKeyboard) { + if (activeKeyboard == this) return; + activeKeyboard->clearField(); + } + activeKeyboard = this; + attach(MainWindow::instance()); +} + + +// this routine finds the window that is a FormWindow. This is the window +// that contains all of the editable fields. This is the window that needs +// to be scrolled into view. +FormWindow *Keyboard::findFormWindow() +{ + if (fieldContainer) { + auto children = fieldContainer->getChildren(); + auto k = children.begin(); + while (k != children.end()) + { + FormWindow *a = dynamic_cast(*k); + if (a) + return a; + k++; + } + } + + return nullptr; +} + +void Keyboard::setField(FormField* newField) +{ + attachKeyboard(); + + fieldContainer = getFieldContainer(newField); + if (fieldContainer) { + coord_t newHeight = LCD_H - height(); + fieldContainer->setHeight(newHeight); + + fields = findFormWindow(); + if (fields) { + // scroll the header of the window out of view to get more space to + // see the field being edited + fieldContainer->setScrollPositionY(fields->top()); + oldHeight = fields->height(); + fields->setHeight(newHeight); + + coord_t offsetY = calcScrollOffsetForField(newField, fields); + fields->setScrollPositionY(offsetY); + } + + invalidate(); + newField->setEditMode(true); + field = newField; + } else { + clearField(); + } +} + +Window* Keyboard::getFieldContainer(FormField* field) +{ + return field->getFullScreenWindow(); +} diff --git a/radio/src/thirdparty/libopenui/src/keyboard_base.h b/radio/src/thirdparty/libopenui/src/keyboard_base.h index fb6859eb8..d55c55239 100644 --- a/radio/src/thirdparty/libopenui/src/keyboard_base.h +++ b/radio/src/thirdparty/libopenui/src/keyboard_base.h @@ -1,8 +1,8 @@ /* - * Copyright (C) OpenTX + * Copyright (C) EdgeTX * * Source: - * https://github.com/opentx/libopenui + * https://github.com/edgetx/libopenui * * This file is a part of libopenui library. * @@ -25,61 +25,23 @@ class Keyboard: public FormWindow { public: - explicit Keyboard(coord_t height): - FormWindow(nullptr, {0, LCD_H - height, LCD_W, height}, OPAQUE) + explicit Keyboard(coord_t height) : FormWindow(nullptr, {0, LCD_H - height, LCD_W, height}, OPAQUE) { } - static void hide() - { - if (activeKeyboard) { - activeKeyboard->clearField(); - activeKeyboard = nullptr; - } - } + static void hide(); protected: static Keyboard * activeKeyboard; - FormField * field = nullptr; - Window * fieldContainer = nullptr; + FormField *field = nullptr; + Window *fieldContainer = nullptr; + FormWindow *fields = nullptr; + coord_t oldHeight = 0; - void setField(FormField * newField) - { - if (activeKeyboard) { - if (activeKeyboard == this) - return; - activeKeyboard->clearField(); - } - activeKeyboard = this; - attach(MainWindow::instance()); - fieldContainer = getFieldContainer(newField); - if (fieldContainer) { - fieldContainer->setHeight(LCD_H - height() - fieldContainer->top()); - fieldContainer->scrollTo(newField); - invalidate(); - newField->setEditMode(true); - field = newField; - } else { - clearField(); - } - } - - void clearField() - { - detach(); - if (fieldContainer) { - fieldContainer->setHeight(LCD_H - 0 - fieldContainer->top()); - fieldContainer = nullptr; - } - if (field) { - field->setEditMode(false); - field = nullptr; - } - } - - Window * getFieldContainer(FormField * field) - { - return field->getFullScreenWindow(); - } + void setField(FormField *newField); + void clearField(); + Window *getFieldContainer(FormField * field); + void attachKeyboard(); + FormWindow *findFormWindow(); }; diff --git a/radio/src/thirdparty/libopenui/src/keyboard_text.cpp b/radio/src/thirdparty/libopenui/src/keyboard_text.cpp index cbf3860a5..9f827bbbf 100644 --- a/radio/src/thirdparty/libopenui/src/keyboard_text.cpp +++ b/radio/src/thirdparty/libopenui/src/keyboard_text.cpp @@ -79,7 +79,8 @@ const char * const KEYBOARD_NUMBERS[] = { KEYBOARD_SET_LETTERS KEYBOARD_SPACE KEYBOARD_ENTER }; -const char * const * const KEYBOARD_LAYOUTS[] = { +#define NUMBER_OF_KEYBOARD_LAYOUTS 4 +const char * const * const KEYBOARD_LAYOUTS[NUMBER_OF_KEYBOARD_LAYOUTS] = { KEYBOARD_UPPERCASE, KEYBOARD_LOWERCASE, KEYBOARD_LOWERCASE, @@ -97,70 +98,169 @@ TextKeyboard::~TextKeyboard() _instance = nullptr; } +int TextKeyboard::getCharWidth(const char c) +{ + if (int8_t(c) < 0) return KEYBOARD_BITMAP_WIDTH; + + switch (c) + { + case ' ': return 15; + case KEYBOARD_SPACE[0]: return KEYBOARD_SPACE_WIDTH; + case KEYBOARD_ENTER[0]: return KEYBOARD_ENTER_WIDTH; + default: return KEYBOARD_CHAR_WIDTH; + } +} + + +int TextKeyboard::calculateMaxWidth() +{ + int maxWidth = 0; + for (uint8_t i = 0; i < NUMBER_OF_KEYBOARD_LAYOUTS; i++) { + int lineWidth = 0; + const char * c = layout[i]; + while (*c) { + lineWidth += getCharWidth(*c); + maxWidth = max(lineWidth, maxWidth); + c++; + } + } + + return maxWidth; +} + void TextKeyboard::paint(BitmapBuffer * dc) { lcdSetColor(RGB(0xE0, 0xE0, 0xE0)); dc->clear(CUSTOM_COLOR); - for (uint8_t i=0; i<4; i++) { - coord_t y = 15 + i * 40; - coord_t x = 15; + // center the keyboard + coord_t start_x = (dc->width() - calculateMaxWidth()) / 2; + + for (uint8_t i = 0; i < NUMBER_OF_KEYBOARD_LAYOUTS; i++) { + coord_t y = KEYBOARD_OFFSET_Y + i * KEYBOARD_ROW_HEIGHT; + coord_t x = start_x; const char * c = layout[i]; + while (*c) { - if (*c == ' ') { - x += 15; - } - else if (*c == KEYBOARD_SPACE[0]) { + if (*c == KEYBOARD_SPACE[0]) { // spacebar dc->drawBitmapPattern(x, y, LBM_KEY_SPACEBAR, COLOR_THEME_SECONDARY1); - x += 135; } else if (*c == KEYBOARD_ENTER[0]) { // enter - dc->drawSolidFilledRect(x, y-2, 80, 25, COLOR_THEME_DISABLED); - dc->drawText(x+40, y, "ENTER", CENTERED); - x += 80; + dc->drawSolidFilledRect(x, y - 2, KEYBOARD_ENTER_WIDTH, 25, COLOR_THEME_DISABLED); + dc->drawText(x + KEYBOARD_ENTER_WIDTH / 2, y + 2, "ENTER", CENTERED); } else if (int8_t(*c) < 0) { - dc->drawBitmapPattern(x, y, LBM_SPECIAL_KEYS[uint8_t(*c - 128)], COLOR_THEME_SECONDARY1); - x += 45; + const uint8_t *pBitmap = LBM_SPECIAL_KEYS[uint8_t(*c - 128)]; + uint8_t bitmapWidth = *pBitmap, bitmapHeight = *(pBitmap + 2); + if (touched && *c == touch_key) { + dc->drawSolidFilledRect(x - 2, y + 3, bitmapWidth + 4, bitmapHeight + 4, COLOR_THEME_SECONDARY2); + } + + dc->drawBitmapPattern(x, y + 5, pBitmap, COLOR_THEME_SECONDARY1); } else { - dc->drawSizedText(x, y, c, 1); - x += 30; + if (touched && *c == touch_key) { + dc->drawSolidFilledRect(x - KEYBOARD_CHAR_WIDTH / 2, y - ((KEYBOARD_ROW_HEIGHT - 16) / 2), KEYBOARD_CHAR_WIDTH, KEYBOARD_ROW_HEIGHT, COLOR_THEME_FOCUS); + } + dc->drawSizedText(x, y, c, 1, CENTERED); } + x += getCharWidth(*c); c++; } } } +bool TextKeyboard::onTouchStart(coord_t x, coord_t y) +{ + // remove the centering. If x < 0 then this is an invalid touch event + x -= (this->width() - calculateMaxWidth()) / 2; + if (x < 0) { + if (touched) { + touched = false; + invalidate(); + } + return true; + } + + touched = false; + + uint8_t row = max(0, y - 5) / KEYBOARD_ROW_HEIGHT; + const char * key = layout[row]; + + while(*key) { + if (*key == KEYBOARD_SPACE[0] && x <= KEYBOARD_SPACE_WIDTH) { + touch_key = KEYBOARD_SPACE[0]; + touched = true; + break; + } + else if (*key == KEYBOARD_ENTER[0] && x <= KEYBOARD_ENTER_WIDTH) { + touch_key = KEYBOARD_ENTER[0]; + touched = true; + break; + } + else if (int8_t(*key) < 0 && x <= KEYBOARD_BITMAP_WIDTH) { + if (uint8_t(*key) == 128) { + touch_key = KEYBOARD_BACKSPACE[0]; + touched = true; + break; + } else { + touched = false; + break; + } + } + else if (*key != ' ' && (x >= -(KEYBOARD_CHAR_WIDTH / 2) && x <= KEYBOARD_CHAR_WIDTH / 2)) { + touch_key = *key; + touched = true; + break; + } + + x -= getCharWidth(*key); + key++; + + if (touched) { + invalidate(); + break; + } + } + return true; +} + bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) { + // remove the centering width, if x < 0 then this is an invalid touch on a centered keyboard. + x -= (this->width() - calculateMaxWidth()) / 2; + if (x < 0) { + touched = false; + return true; + } + onKeyPress(); - uint8_t row = max(0, y - 5) / 40; + if (touched) + invalidate(); + touched = false; + + uint8_t row = max(0, y - 5) / KEYBOARD_ROW_HEIGHT; + const char * key = layout[row]; while (*key) { - if (*key == ' ') { - x -= 15; - } - else if (*key == KEYBOARD_SPACE[0]) { - if (x <= 135) { + if (*key == KEYBOARD_SPACE[0]) { + if (x <= KEYBOARD_SPACE_WIDTH) { pushEvent(EVT_VIRTUAL_KEY(' ')); return true; } - x -= 135; } else if (*key == KEYBOARD_ENTER[0]) { - if (x <= 80) { + if (x <= KEYBOARD_ENTER_WIDTH) { // enter hide(); return true; } - x -= 80; } else if (int8_t(*key) < 0) { - if (x <= 45) { + if (x <= KEYBOARD_BITMAP_WIDTH) { uint8_t specialKey = *key; if (specialKey == 128) { // backspace @@ -172,17 +272,32 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) } break; } - x -= 45; } else { - if (x <= 30) { + if (*key != ' ' && (x >= -(KEYBOARD_CHAR_WIDTH / 2) && x <= KEYBOARD_CHAR_WIDTH / 2)) { pushEvent(EVT_VIRTUAL_KEY(*key)); return true; } - x -= 30; } + x -= getCharWidth(*key); key++; } return true; } + +bool TextKeyboard::onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) +{ + if (touched) + invalidate(); + + touched = false; + return false; +} + +#if defined(HARDWARE_KEYS) +void TextKeyboard::onEvent(event_t event) +{ + +} +#endif diff --git a/radio/src/thirdparty/libopenui/src/keyboard_text.h b/radio/src/thirdparty/libopenui/src/keyboard_text.h index afc8cfef2..732e6e2c3 100644 --- a/radio/src/thirdparty/libopenui/src/keyboard_text.h +++ b/radio/src/thirdparty/libopenui/src/keyboard_text.h @@ -29,6 +29,13 @@ #define KEYBOARD_SET_LETTERS "\203" #define KEYBOARD_SET_NUMBERS "\204" +#define KEYBOARD_OFFSET_Y 15 +#define KEYBOARD_ROW_HEIGHT 40 +#define KEYBOARD_CHAR_WIDTH 30 +#define KEYBOARD_SPACE_WIDTH 135 +#define KEYBOARD_ENTER_WIDTH 80 +#define KEYBOARD_BITMAP_WIDTH 45 + class TextKeyboard : public Keyboard { public: TextKeyboard(); @@ -53,10 +60,22 @@ class TextKeyboard : public Keyboard { #if defined(HARDWARE_TOUCH) bool onTouchEnd(coord_t x, coord_t y) override; + bool onTouchStart(coord_t x, coord_t y) override; + bool onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) override; #endif protected: +#if defined(HARDWARE_KEYS) + void onEvent(event_t event) override; +#endif + static TextKeyboard * _instance; const char * const * layout; + private: + int calculateMaxWidth(); + int getCharWidth(const char c); + + char touch_key = -128; + bool touched = false; }; diff --git a/radio/src/thirdparty/libopenui/src/numberedit.cpp b/radio/src/thirdparty/libopenui/src/numberedit.cpp index 91d87e3bf..f9d56fbbd 100644 --- a/radio/src/thirdparty/libopenui/src/numberedit.cpp +++ b/radio/src/thirdparty/libopenui/src/numberedit.cpp @@ -103,8 +103,13 @@ void NumberEdit::onEvent(event_t event) } #endif - case EVT_KEY_FIRST(KEY_EXIT): - return; + case EVT_KEY_FIRST(KEY_EXIT): +#if defined(SOFTWARE_KEYBOARD) + Keyboard::hide(); + return; +#endif + break; + #if defined(HARDWARE_TOUCH) case EVT_VIRTUAL_KEY_PLUS: @@ -167,11 +172,3 @@ bool NumberEdit::onTouchEnd(coord_t, coord_t) } #endif -void NumberEdit::onFocusLost() -{ -#if defined(SOFTWARE_KEYBOARD) - Keyboard::hide(); -#endif - - FormField::onFocusLost(); -} diff --git a/radio/src/thirdparty/libopenui/src/numberedit.h b/radio/src/thirdparty/libopenui/src/numberedit.h index 67ff5da90..180889c90 100644 --- a/radio/src/thirdparty/libopenui/src/numberedit.h +++ b/radio/src/thirdparty/libopenui/src/numberedit.h @@ -67,8 +67,6 @@ class NumberEdit : public BaseNumberEdit bool onTouchEnd(coord_t x, coord_t y) override; #endif - void onFocusLost() override; - protected: std::function displayFunction; std::string prefix; diff --git a/radio/src/thirdparty/libopenui/src/textedit.cpp b/radio/src/thirdparty/libopenui/src/textedit.cpp index 45d4997dd..b2aa9ef94 100644 --- a/radio/src/thirdparty/libopenui/src/textedit.cpp +++ b/radio/src/thirdparty/libopenui/src/textedit.cpp @@ -171,6 +171,11 @@ void TextEdit::onEvent(event_t event) case EVT_KEY_BREAK(KEY_EXIT): changeEnd(); FormField::onEvent(event); + +#if defined(SOFTWARE_KEYBOARD) + Keyboard::hide(); +#endif + setFocus(SET_FOCUS_DEFAULT, this); break; case EVT_KEY_LONG(KEY_ENTER): diff --git a/radio/src/thirdparty/libopenui/src/window.h b/radio/src/thirdparty/libopenui/src/window.h index a3cbbb964..6902eb4e4 100644 --- a/radio/src/thirdparty/libopenui/src/window.h +++ b/radio/src/thirdparty/libopenui/src/window.h @@ -144,6 +144,11 @@ class Window focusHandler = std::move(handler); } + const std::list getChildren() + { + return children; + } + virtual void deleteLater(bool detach = true, bool trash = true); void clear();