Merge pull request #14 from kevinkoenig/software-keyboard

Software keyboard
This commit is contained in:
Raphael Coeffic
2021-08-29 18:33:34 +02:00
committed by GitHub
9 changed files with 320 additions and 92 deletions
+1
View File
@@ -42,5 +42,6 @@ if(SOFTWARE_KEYBOARD)
${LIBOPENUI_SRC}
keyboard_text.cpp
keyboard_number.cpp
keyboard_base.cpp
)
endif()
+126
View File
@@ -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<FormWindow*>(*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();
}
+13 -51
View File
@@ -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();
};
+144 -29
View File
@@ -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<coord_t>(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<coord_t>(0, y - 5) / 40;
if (touched)
invalidate();
touched = false;
uint8_t row = max<coord_t>(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
+19
View File
@@ -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;
};
+7 -10
View File
@@ -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();
}
-2
View File
@@ -67,8 +67,6 @@ class NumberEdit : public BaseNumberEdit
bool onTouchEnd(coord_t x, coord_t y) override;
#endif
void onFocusLost() override;
protected:
std::function<void(BitmapBuffer *, LcdFlags, int)> displayFunction;
std::string prefix;
+5
View File
@@ -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):
+5
View File
@@ -144,6 +144,11 @@ class Window
focusHandler = std::move(handler);
}
const std::list<Window *> getChildren()
{
return children;
}
virtual void deleteLater(bool detach = true, bool trash = true);
void clear();