Merge pull request #14 from kevinkoenig/software-keyboard
Software keyboard
This commit is contained in:
@@ -42,5 +42,6 @@ if(SOFTWARE_KEYBOARD)
|
||||
${LIBOPENUI_SRC}
|
||||
keyboard_text.cpp
|
||||
keyboard_number.cpp
|
||||
keyboard_base.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user