Merge pull request #18 from 0xFEEDC0DE64/keyboard

Implemented basic keyboard
This commit is contained in:
2022-06-14 13:56:33 +02:00
committed by GitHub
11 changed files with 444 additions and 54 deletions

View File

@ -30,6 +30,7 @@ set(headers
src/graphdisplay.h
src/icon.h
src/iconinterface.h
src/keyboardhelper.h
src/icons/back.h
src/icons/checked.h
src/icons/unchecked.h

View File

@ -8,15 +8,24 @@ void ChangeValueDisplayInterface::initScreen()
{
Base::initScreen();
tft.drawRect(25, 75, 190, 65, TFT_WHITE);
tft.drawRoundRect(35, 65, 190, 65, 8, TFT_WHITE);
m_valueLabel.start();
tft.setTextFont(4);
tft.setTextColor(TFT_WHITE);
tft.drawString("Change value and", 10, 160);
tft.drawString("press button to", 10, 185);
tft.drawString("confirm and go", 10, 210);
tft.drawString("back.", 10, 235);
if (espgui::isLandscape())
{
tft.drawString("Change value and press", 10, 152);
tft.drawString("button to confirm and", 10, 177);
tft.drawString("go back", 10, 202);
}
else
{
tft.drawString("Change value and", 10, 160);
tft.drawString("press button to", 10, 185);
tft.drawString("confirm and go", 10, 210);
tft.drawString("back.", 10, 235);
}
}
template<>

View File

@ -33,7 +33,7 @@ public:
virtual void setShownValue(int value) = 0;
protected:
Label m_valueLabel{26, 81}; // 188, 53
Label m_valueLabel{36, 71}; // 188, 53
};
template<typename Tvalue>

View File

@ -40,7 +40,7 @@ private:
T m_value;
bool m_pressed{};
Label m_valueLabel{26, 81}; // 188, 53
Label m_valueLabel{36, 71}; // 188, 53
};
template<typename T>
@ -58,15 +58,24 @@ void ChangeValueDisplayChrono<T>::initScreen()
{
Base::initScreen();
tft.drawRect(25, 75, 190, 65, TFT_WHITE);
tft.drawRoundRect(32, 65, 190, 34, 8, TFT_WHITE);
m_valueLabel.start();
tft.setTextFont(4);
tft.setTextColor(TFT_WHITE);
tft.drawString("Change value and", 10, 160);
tft.drawString("press button to", 10, 185);
tft.drawString("confirm and go", 10, 210);
tft.drawString("back.", 10, 235);
if (espgui::isLandscape())
{
tft.drawString("Change value and press", 10, 152);
tft.drawString("button to confirm and", 10, 177);
tft.drawString("go back", 10, 202);
}
else
{
tft.drawString("Change value and", 10, 160);
tft.drawString("press button to", 10, 185);
tft.drawString("confirm and go", 10, 210);
tft.drawString("back.", 10, 235);
}
}
template<typename T>

View File

@ -19,7 +19,7 @@ void ChangeValueDisplay<wifi_stack::ip_address_t>::initScreen()
Base::initScreen();
tft.setTextColor(TFT_WHITE);
tft.drawString("Change IP", 0, 50);
tft.drawString("Change IP Address", 0, 50);
for(int i = 0; i <= 3; i++)
{
@ -30,6 +30,10 @@ void ChangeValueDisplay<wifi_stack::ip_address_t>::initScreen()
for (auto &label : m_labels)
label.start();
tft.drawString(".", spacing+boxWidth+spacing/4, y);
tft.drawString(".", spacing*2+boxWidth*2+spacing/4, y);
tft.drawString(".", spacing*3+boxWidth*3+spacing/4, y);
drawRect(m_currentIndex, 1, TFT_YELLOW);
drawRect(m_currentIndex, 2, TFT_YELLOW);
@ -108,7 +112,7 @@ void ChangeValueDisplay<wifi_stack::ip_address_t>::buttonReleased(Button button)
void ChangeValueDisplay<wifi_stack::ip_address_t>::drawRect(int index, int offset, uint32_t color) const
{
tft.drawRect(m_labels[index].x()-offset, m_labels[index].y()-offset, boxWidth+(offset*2), boxHeight+(offset*2), color);
tft.drawRoundRect(m_labels[index].x()-offset, m_labels[index].y()-offset, boxWidth+(offset*2), boxHeight+(offset*2), 3, color);
}
} // namespace espgui

View File

@ -1,5 +1,8 @@
#include "changevaluedisplay_string.h"
// 3rdparty lib includes
#include <espchrono.h>
// local includes
#include "tftinstance.h"
@ -8,63 +11,62 @@ void espgui::ChangeValueDisplay<std::string>::start()
Base::start();
m_value = this->getValue();
m_pressed = false;
}
void espgui::ChangeValueDisplay<std::string>::initScreen()
{
Base::initScreen();
tft.drawRect(25, 75, 190, 65, TFT_WHITE);
tft.drawRoundRect(10, 50, tft.width() - 20, 34, 5, TFT_WHITE);
m_valueLabel.start();
tft.setTextFont(4);
tft.setTextColor(TFT_WHITE);
tft.drawString("Change value and", 10, 160);
tft.drawString("press button to", 10, 185);
tft.drawString("confirm and go", 10, 210);
tft.drawString("back.", 10, 235);
}
void espgui::ChangeValueDisplay<std::string>::update()
{
Base::update();
if (m_pressed)
{
m_pressed = false;
if (auto result = this->setValue(m_value); result)
confirm();
else
errorOccured(std::move(result).error());
}
m_keyboard.start();
}
void espgui::ChangeValueDisplay<std::string>::redraw()
{
const auto now = espchrono::millis_clock::now().time_since_epoch().count() / 1000;
Base::redraw();
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextFont(4);
m_valueLabel.redraw(m_value);
tft.drawRect(m_valueLabel.x() + tft.textWidth(m_value) + 3, m_valueLabel.y(), 2, tft.fontHeight(), (now % 1000 < 500) ? TFT_WHITE : TFT_BLACK);
}
void espgui::ChangeValueDisplay<std::string>::setShownValue(std::string &&value)
{
tft.drawRect(m_valueLabel.x() + tft.textWidth(m_value) + 3, m_valueLabel.y(), 2, tft.fontHeight(), TFT_BLACK);
m_value = std::move(value);
}
void espgui::ChangeValueDisplay<std::string>::buttonPressed(Button button)
{
//Base::buttonPressed(button);
switch (button)
{
case Button::Left: this->back(); break;
case Button::Right: m_pressed = true; break;
default:;
}
m_keyboard.buttonPressed(button);
}
void espgui::ChangeValueDisplay<std::string>::buttonReleased(Button button)
{
//Base::buttonReleased(button);
m_keyboard.buttonReleased(button);
// TODO stop auto scroll
}
void espgui::ChangeValueDisplay<std::string>::confirmValue()
{
if (auto result = this->setValue(m_value); result)
confirm();
else
errorOccured(std::move(result).error());
}
void espgui::ChangeValueDisplay<std::string>::removeLastCharFromShownValue()
{
if (auto val = this->shownValue(); !val.empty())
{
val.pop_back();
this->setShownValue(std::move(val));
}
}

View File

@ -4,11 +4,12 @@
#include <string>
// local includes
#include "changevaluedisplay.h"
#include "displaywithtitle.h"
#include "confirminterface.h"
#include "backinterface.h"
#include "changevaluedisplay.h"
#include "confirminterface.h"
#include "displaywithtitle.h"
#include "errorhandlerinterface.h"
#include "keyboardhelper.h"
#include "widgets/label.h"
namespace espgui {
@ -29,20 +30,24 @@ public:
void start() override;
void initScreen() override;
void update() override;
void redraw() override;
void buttonPressed(Button button) override;
void buttonReleased(Button button) override;
void confirmValue();
const std::string &shownValue() const { return m_value; }
void setShownValue(std::string &&value) { m_value = std::move(value); }
void setShownValue(std::string &&value);
void appendToShownValue(char c) { m_value.push_back(c); }
void appendToShownValue(const std::string &s) { m_value.append(s); }
void removeLastCharFromShownValue();
private:
std::string m_value;
bool m_pressed{};
Label m_valueLabel{26, 81}; // 188, 53
Label m_valueLabel{12, 55}; // 188, 53
Keyboard<ChangeValueDisplay<std::string>> m_keyboard{*this};
};
} // namespace espgui

355
src/keyboardhelper.h Normal file
View File

@ -0,0 +1,355 @@
#pragma once
// esp-idf includes
#include <esp_log.h>
// 3rdparty lib includes
#include <fmt/core.h>
#include <strutils.h>
// local includes
#include "display.h"
#include "screenmanager.h"
#include "tftinstance.h"
namespace espgui {
namespace {
constexpr const char * const TAG = "KeyboardHelper";
// all ascii chars from '!' to '~'
constexpr const char * const KEYBOARD_SCREEN_1 = "\"!$%&/()=?QWERTZUIOPASDFGHJKL YXCVBNM";
constexpr const char * const KEYBOARD_SCREEN_2 = "1234567890qwertzuiopasdfghjkl yxcvbnm";
constexpr const char * const KEYBOARD_SCREEN_3 = "#'*+-,.;:_<|>\\@[]^_`{}~\"!$%&/ ()=?+-*";
constexpr const char * const SHIFT = "Shift";
constexpr const char * const SPACE = "Space";
constexpr const char * const BACKSPACE = "Back";
constexpr const char * const ENTER = "Enter";
} // namespace
template<typename TDisplay = espgui::Display>
class Keyboard
{
public:
explicit Keyboard(TDisplay &display) : m_display(display) {}
void start();
void buttonPressed(Button button);
void buttonReleased(Button button);
void moveSelectorUp();
void moveSelectorDown();
void moveSelectorLeft();
void moveSelectorRight();
private:
void updateCharLength();
void drawKeyboard(bool dont_draw_string = false);
void nextScreen();
int32_t m_char_length{0};
std::string m_keyboard{KEYBOARD_SCREEN_1};
std::string m_keyset{};
TDisplay &m_display;
int32_t m_keyboard_start_y{0};
int32_t m_char_index{10};
int32_t m_last_char_index{-1};
enum class Screen : uint8_t {
SCREEN_1,
SCREEN_2,
SCREEN_3,
SCREEN_MAX
};
Screen m_current_screen{Screen::SCREEN_2};
};
template<typename TDisplay>
void Keyboard<TDisplay>::moveSelectorUp()
{
m_last_char_index = m_char_index;
m_char_index -= 10;
if (m_char_index < 0)
m_char_index = (m_char_length + 4) - m_char_index;
}
template<typename TDisplay>
void Keyboard<TDisplay>::moveSelectorDown()
{
m_last_char_index = m_char_index;
m_char_index += 10;
if (m_char_index >= (m_char_length + 4))
m_char_index = m_char_index - (m_char_length + 4);
}
template<typename TDisplay>
void Keyboard<TDisplay>::nextScreen()
{
m_current_screen = static_cast<Screen>(static_cast<uint8_t>(m_current_screen) + uint8_t{1});
if (m_current_screen >= Screen::SCREEN_MAX)
m_current_screen = Screen::SCREEN_1;
updateCharLength();
start();
}
template<typename TDisplay>
void Keyboard<TDisplay>::drawKeyboard(bool dont_draw_string)
{
size_t char_index{0};
std::string keyboard_screen{m_keyboard};
std::vector<std::string> keyboard_lines;
for (size_t i = 0; i < keyboard_screen.size(); i += 10)
{
std::string line = keyboard_screen.substr(i, 10);
if (cpputils::stringEndsWith(line, " "))
line.pop_back();
keyboard_lines.push_back(line);
}
const auto datum = tft.getTextDatum();
tft.setTextDatum(MC_DATUM);
for (size_t i = 0; i < keyboard_lines.size(); i++)
{ tft.setTextColor(TFT_WHITE);
tft.setTextColor(TFT_GREY);
const int32_t y = m_keyboard_start_y + (i * tft.fontHeight() + 9);
std::string line = keyboard_lines[i];
const int16_t x = tft.width() / (line.size() + 1);
for (size_t j = 0; j < line.size(); j++)
{
const std::string _char{line[j]};
const int32_t x_pos = x * (j + 1);
const int32_t y_pos = y;
const auto width = tft.textWidth(_char) + 2;
const auto height = tft.fontHeight() + 4;
if (char_index == m_char_index)
tft.drawRoundRect(x_pos-width/2-1, y_pos-height/2, width+2, height-4, 3, TFT_DARKGREY);
if (char_index == m_last_char_index)
{
tft.drawRoundRect(x_pos-width/2-1, y_pos-height/2, width+2, height-4, 3, TFT_BLACK);
}
if (!dont_draw_string || char_index == m_char_index || char_index == m_last_char_index)
{
if (char_index == m_char_index || char_index == m_last_char_index)
tft.setTextColor(char_index == m_last_char_index ? TFT_GREY : TFT_WHITE);
tft.drawString(_char, x_pos, y_pos);
if (char_index == m_char_index)
tft.setTextColor(TFT_GREY);
}
char_index++;
}
}
tft.setTextDatum(datum);
// draw 3 extra buttons, back, space and enter (x=10, x=tft.width()/2, x=tft.width()-10)
const int32_t y = m_keyboard_start_y + (keyboard_lines.size() * tft.fontHeight());
if (isLandscape())
{
// align left (SHIFT, SPACE)
tft.drawRoundRect(15 - 2, y - 1, tft.textWidth(SHIFT) + 4, tft.fontHeight() + 2, 3, TFT_DARKGREY);
tft.drawRoundRect(30 + tft.textWidth(SHIFT) - 2, y - 1, tft.textWidth(SPACE) + 4, tft.fontHeight() + 2, 3,
TFT_DARKGREY);
// align right (BACKSPACE, ENTER); align from tft.width()
tft.drawRoundRect(tft.width() - 30 - tft.textWidth(ENTER) - tft.textWidth(BACKSPACE) - 2, y - 1,
tft.textWidth(BACKSPACE) + 4, tft.fontHeight() + 2, 3, TFT_DARKGREY);
tft.drawRoundRect(tft.width() - 15 - tft.textWidth(ENTER) - 2, y - 1, tft.textWidth(ENTER) + 4,
tft.fontHeight() + 2, 3, TFT_DARKGREY);
// if (!dont_draw_string)
{
// align left (SHIFT, SPACE)
if (m_char_index == m_char_length)
tft.setTextColor(TFT_BLACK, TFT_WHITE);
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(SHIFT, 15, y);
if (m_char_index == m_char_length + 1)
tft.setTextColor(TFT_BLACK, TFT_WHITE);
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(SPACE, 30 + tft.textWidth(SHIFT), y);
// align right (BACKSPACE, ENTER); align from tft.width()
if (m_char_index == m_char_length + 2)
tft.setTextColor(TFT_BLACK, TFT_WHITE);
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(BACKSPACE, tft.width() - 30 - tft.textWidth(ENTER) - tft.textWidth(BACKSPACE), y);
if (m_char_index == m_char_length + 3)
tft.setTextColor(TFT_BLACK, TFT_WHITE);
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(ENTER, tft.width() - 15 - tft.textWidth(ENTER), y);
}
}
else
{
const int32_t y_2 = y + tft.fontHeight() + 4;
// align left (SHIFT, SPACE)
tft.drawRoundRect(15 - 2, y - 1, tft.textWidth(SHIFT) + 4, tft.fontHeight() + 2, 3, TFT_DARKGREY);
tft.drawRoundRect(15 - 2, y_2 - 1, tft.textWidth(SPACE) + 4, tft.fontHeight() + 2, 3, TFT_DARKGREY);
// align right (BACKSPACE, ENTER); align from tft.width()
tft.drawRoundRect(tft.width() - 15 - tft.textWidth(ENTER) - 2, y - 1, tft.textWidth(ENTER) + 4,
tft.fontHeight() + 2, 3, TFT_DARKGREY);
tft.drawRoundRect(tft.width() - 15 - tft.textWidth(BACKSPACE) - 2, y_2 - 1, tft.textWidth(BACKSPACE) + 4,
tft.fontHeight() + 2, 3, TFT_DARKGREY);
// if (!dont_draw_string)
{
// align left (SHIFT, SPACE)
if (m_char_index == m_char_length)
tft.setTextColor(TFT_BLACK, TFT_WHITE);
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(SHIFT, 15, y);
if (m_char_index == m_char_length + 1)
tft.setTextColor(TFT_BLACK, TFT_WHITE);
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(SPACE, 15, y_2);
// align right (BACKSPACE, ENTER); align from tft.width()
if (m_char_index == m_char_length + 2)
tft.setTextColor(TFT_BLACK, TFT_WHITE);
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(BACKSPACE, tft.width() - 15 - tft.textWidth(BACKSPACE), y_2);
if (m_char_index == m_char_length + 3)
tft.setTextColor(TFT_BLACK, TFT_WHITE);
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(ENTER, tft.width() - 15 - tft.textWidth(ENTER), y);
}
}
}
template<typename TDisplay>
void Keyboard<TDisplay>::updateCharLength()
{
std::string tmpstr;
switch (m_current_screen)
{
case Screen::SCREEN_1:
tmpstr = KEYBOARD_SCREEN_1;
break;
case Screen::SCREEN_2:
tmpstr = KEYBOARD_SCREEN_2;
break;
case Screen::SCREEN_3:
tmpstr = KEYBOARD_SCREEN_3;
break;
default:
ESP_LOGE(TAG, "Unknown screen");
return;
}
m_keyboard = tmpstr;
cpputils::stringReplaceAll(" ", "", tmpstr);
m_char_length = tmpstr.length();
m_keyset = tmpstr;
}
template<typename TDisplay>
void Keyboard<TDisplay>::moveSelectorRight()
{
m_last_char_index = m_char_index;
if (m_char_index == (m_char_length + 4) - 1)
m_char_index = 0;
else
m_char_index++;
}
template<typename TDisplay>
void Keyboard<TDisplay>::moveSelectorLeft()
{
m_last_char_index = m_char_index;
if (m_char_index == 0)
m_char_index = (m_char_length + 4) - 1;
else
m_char_index--;
}
template<typename TDisplay>
void Keyboard<TDisplay>::start()
{
const auto isLandscape = espgui::isLandscape();
m_keyboard_start_y = isLandscape ? 98 : 120;
tft.fillRect(1, m_keyboard_start_y - 10, tft.width()-1, tft.height() - m_keyboard_start_y - (isLandscape ? 0 : 30), TFT_BLACK);
tft.drawSunkenRect(1, m_keyboard_start_y - 10, tft.width()-1, tft.height() - m_keyboard_start_y - (isLandscape ? 0 : 30), TFT_WHITE, TFT_GREY, TFT_BLACK);
updateCharLength();
drawKeyboard();
}
template<typename TDisplay>
void Keyboard<TDisplay>::buttonPressed(Button button)
{
switch (button)
{
case Right:
{
if (m_char_index < m_char_length)
m_display.setShownValue(m_display.shownValue() + m_keyset[m_char_index]);
else if (m_char_index == m_char_length) // shift
{
nextScreen();
}
else if (m_char_index == m_char_length + 1) // space
{
m_display.setShownValue(m_display.shownValue() + " ");
}
else if (m_char_index == m_char_length + 2) // backspace
{
m_display.removeLastCharFromShownValue();
}
else if (m_char_index == m_char_length + 3) // enter
{
m_display.confirmValue();
}
break;
}
case Left:
popScreen();
return;
case Up:
moveSelectorLeft();
drawKeyboard(true);
break;
case Down:
moveSelectorRight();
drawKeyboard(true);
break;
default:;
}
}
template<typename TDisplay>
void Keyboard<TDisplay>::buttonReleased(espgui::Button button) {}
} // namespace espgui

View File

@ -105,10 +105,11 @@ void MenuDisplay::redraw()
int newHighlightedIndex{-1};
const auto drawItemRect = [](const auto &label, const auto color){
tft.drawRect(5,
tft.drawRoundRect(5,
label.y()-1,
tft.width() - 10,
lineHeight+1,
5,
color);
};

View File

@ -2,4 +2,4 @@
namespace espgui {
TFT_eSPI tft;
}
} // namespace espgui

View File

@ -5,4 +5,8 @@
namespace espgui {
extern TFT_eSPI tft;
inline bool isLandscape()
{
return (tft.getRotation() == 1 || tft.getRotation() == 3);
}
}