I’ve cleaned it up a bit without necessarily considering if there’s a better way to do it:
#include <Pokitto.h>
#include <cstdint>
enum class Action : std::uint8_t
{
None,
UpperA,
UpperB,
UpperC,
UpperD,
UpperE,
UpperF,
UpperG,
UpperH,
UpperI,
UpperJ,
UpperK,
UpperL,
UpperM,
UpperN,
UpperO,
UpperP,
UpperQ,
UpperR,
UpperS,
UpperT,
UpperU,
UpperV,
UpperW,
UpperX,
UpperY,
UpperZ,
LowerA,
LowerB,
LowerC,
LowerD,
LowerE,
LowerF,
LowerG,
LowerH,
LowerI,
LowerJ,
LowerK,
LowerL,
LowerM,
LowerN,
LowerO,
LowerP,
LowerQ,
LowerR,
LowerS,
LowerT,
LowerU,
LowerV,
LowerW,
LowerX,
LowerY,
LowerZ,
Number0,
Number1,
Number2,
Number3,
Number4,
Number5,
Number6,
Number7,
Number8,
Number9,
Period,
Comma,
Semicolon,
Colon,
ExclaimationMark,
QuestionMark,
Quote,
Apostrophe,
SingleQuote,
Caret,
BracketRoundLeft,
BracketRoundRight,
BracketSquareLeft,
BracketSquareRight,
BracketCurlyLeft,
BracketCurlyRight,
BracketAngleLeft,
BracketAngleRight,
EqualsSign,
Minus,
Plus,
Asterisk,
Tilde,
ForwardSlash,
BackSlash,
Underscore,
VerticalBar,
HashSymbol,
DollarSign,
EuroSign,
PoundSign,
PercentSign,
Ampersand,
AtSign,
Space,
Tab,
NewLine,
Special,
Backspace,
Delte,
Cancel,
Shift,
Control,
ArrowLeft,
ArrowRight,
ArrowUp,
ArrowDown,
SwitchPage
};
const char actionChars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,;:!?\"'`^()[]{}<>=-+*~/\\_|#$%&@ \t\n ";
#define A(x) Action::##x
#define AU(x) Action::Upper##x
#define AL(x) Action::Lower##x
const Action keyboardLayoutPage1[] =
{
A(Cancel),
A(Number0), A(Number1), A(Number2), A(Number3), A(Number4), A(Number5), A(Number6), A(Number7), A(Number8), A(Number9),
A(Backspace), A(Delete), A(BracketAngleLeft), A(BracketAngleRight), A(Tab), AU(A), AU(B), AU(C), AU(D), AU(E), AU(F), AU(G), AU(H), AU(I), AU(J), A(NewLine), A(Ampersand), A(Plus), A(Minus), A(Shift), AU(K), AU(L), AU(M), AU(N), AU(O), AU(P), AU(Q), AU(R), AU(S), AU(T), A(ArrowUp), A(At), A(EqualsSign), A(Asterisk), A(Control), AU(U), AU(V), AU(W), AU(X), AU(Y), AU(Z), A(Space), A(Underscore), A(SwitchPage), A(ArrowLeft), A(ArrowDown), A(ArrowRight), A(ForwardSlash), A(BackSlash)
};
const Action keyboardLayoutPage2[] =
{
A(Cancel), A(Period), A(Comma), A(Semicolon), A(COLON), A(ExclamationMark), A(QuestionMark), A(Quotes), A(Apostrophe), A(SingleQuote), A(Caret), A(BACKSPACE), A(DELETE), A(BRACKET_LEFT_ROUND), A(BracketRoundRight), A(Tab), AL(A), AL(B), AL(C), AL(D), AL(E), AL(F), AL(G), AL(H), AL(I), AL(J), A(NewLine), A(PercentSign), A(BracketSquareLeft), A(BracketSquareRight), A(Shift), AL(K), AL(L), AL(M), AL(N), AL(O), AL(P), AL(Q), AL(R), AL(S), AL(T), A(ArrowUp), A(DollarSign), A(BracketCurlyLeft), A(BracketCurlyRight), A(Control), AL(U), AL(V), AL(W), AL(X), AL(Y), AL(Z), A(Space), A(HashSymbol), A(SwitchPage), A(ArrowLeft), A(ArrowDown), A(ArrowRight), A(Tilde), A(Bar)
};
#undef A
#undef AU
#undef AL
const uint8_t actionIcons[] =
{
8,5,11,
0b00100000,
0b01111000,
0b11000000,
0b01111000,
0b00100000,
0b00100000,
0b11110000,
0b00011000,
0b11110000,
0b00100000,
0b11011000,
0b01110000,
0b00100000,
0b01110000,
0b11011000,
0b01000000,
0b11100000,
0b00000000,
0b11100000,
0b01000000,
0b00100000,
0b01010000,
0b10001000,
0b11111000,
0b00000000,
0b00100000,
0b01100000,
0b11100000,
0b01100000,
0b00100000,
0b00100000,
0b00110000,
0b00111000,
0b00110000,
0b00100000,
0b00100000,
0b01110000,
0b11111000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b11111000,
0b01110000,
0b00100000,
0b11100000,
0b11001000,
0b10011000,
0b00111000,
0b00000000,
0,0,0,0,0,0,0,0
};
class Keyboard
{
protected:
std::size_t cursorX = 0;
std::size_t cursorY = 0;
Action currentAction = Action::None;
const Action * pageLayout = keyboardLayoutPage1;
std::size_t getIndex(std::size_t x, std::size_t y) const
{
return ((y * Keyboard::HorizontalKeyCount) + x);
}
bool buttonTriggered(std::uint8_t button) const
{
return (Pokitto::Buttons::pressed(button) || Pokitto::Buttons::repeat(button, 2));
}
public:
static constexpr std::size_t HorizontalKeyCount = 15;
static constexpr std::size_t VerticalKeyCount = 4;
static constexpr std::size_t KeySize = 6;
static constexpr std::size_t FirstKeyX = 0;
static constexpr std::size_t LastKeyX = (HorizontalKeyCount - 1);
static constexpr std::size_t FirstKeyY = 0;
static constexpr std::size_t LastKeyY = (VerticalKeyCount - 1);
Keyboard(void) = default;
Action getCurrentAction(void) const
{
return this->currentAction;
}
void update()
{
this->currentAction = ACTION_NONE;
if (this->buttonTriggered(BTN_RIGHT))
{
if(this->cursorY < LastKeyX)
++this->cursorX;
else
this->cursorX = FirstKeyX;
}
else if (this->buttonTriggered(BTN_LEFT))
{
if(this->cursorX > FirstKeyX)
--this->cursorX;
else
this->cursorX = LastKeyX;
}
if (this->buttonTriggered(BTN_DOWN))
{
if(this->cursorY < LastKeyY)
++this->cursorY;
else
this->cursorY = FirstKeyY;
}
else if (this->buttonTriggered(BTN_UP))
{
if(this->cursorY > FirstKeyY)
--this->cursorY;
else
this->cursorY = LastKeyY;
}
if (Pokitto::Buttons::pressed(BTN_A))
{
this->currentAction = this->pageLayout[this->getIndex(this->cursorX, this->cursorY)];
if (this->currentAction == Action::SwitchPage)
{
this->pageLayout = (this->pageLayout == keyboardLayoutPage1) ? keyboardLayoutPage2 : keyboardLayoutPage1;
this->currentAction = Action::None;
}
}
}
void draw(int x, int y)
{
using Pokitto::Display;
Display::setFont(fontTiny);
for (std::size_t keyY = 0; keyY < Keyboard::VerticalKeyCount; ++keyY)
{
const std::int16_t drawY = static_cast<std::int16_t>(static_cast<std::size_t>(y) + (keyY * (Keyboard::KeySize + 1)));
for (std::size_t keyX = 0; keyX < Keyboard::HorizontalKeyCount; ++keyX)
{
const std::int16_t drawX = static_cast<std::int16_t>(static_cast<std::size_t>(x) + (keyX * (Keyboard::KeySize + 1)));
Action action = this->pageLayout[this->getIndex(keyX, keyY)];
if (action <= Action::Special)
{
Display::setCursor(drawX), drawY);
Display::print(actionChars[action]);
}
else
{
Display::drawMonoBitmap(drawX, drawY, actionIcons, action - static_cast<int>(Action::Special) - 1);
}
if (keyX == this->cursorX && keyY == this->cursorY)
Display::drawRect(drawX, drawY, Keyboard::KeySize, Keyboard::KeySize);
}
}
}
};
int main(void)
{
using Pokitto::Core
using Pokitto::Display;
Core::begin();
Keyboard keyboard;
std::array<char, 17> buffer;
std::size_t bufferIndex = 0;
buffer.fill('\0');
while (Core::isRunning())
{
if (!Core::update())
continue;
keyboard.update();
Action action = keyboard.getAction();
if (action != Action::None)
{
if (action < Action::Special)
{
buffer[bufferIndex] = actionChars[action];
if(bufferIndex < (buffer.size() - 1))
++bufferIndex;
else
bufferIndex = 0;
}
else
{
switch (action)
{
case Action::Backspace:
if(bufferIndex > 0)
--bufferIndex;
else
bufferIndex = (buffer.size() - 2);
buffer[bufferIndex] = ' ';
break;
default: break;
}
}
}
keyboard.draw(2, 5);
Display::setCursor(1, 50);
Display::print(buffer);
}
return 0;
}
Various comments on style
Firstly, the entire reason you prefix all your actions with ACTION_
is overcome by using a scoped enumeration.
By being scoped you no longer have to worry about name clashes.
Secondly, you don’t need to make an instance of Pokitto::Core
.
Pokitto::Core
contains absolutely no non-static state and the constructor is empty, so effectively you’re passing a pointer around to a 1-byte structure, and it’s only 1 byte because it needs to be at least that big to have an address (i.e. a type of size 0 would be illegal).
It’s cheaper and easier to just use all the static functions as static functions.
uint8_t
should be std::uint8_t
because cstdint
only guarantees that the types exist in the std
namespace, it doesn’t guarantee that they exist in the global namespace - that’s implementation specific behaviour.
Either way, the processor is 32 bit so you should be trying to use either std::uint32_t
or unsigned int
unless there’s a very specific reason for using std::uint16_t
/std::uint8_t
.
std::size_t
should be used to index arrays because it’s the correct type to index arrays with due to its definition as being capable of holding the size of any data type.
It also has certain other benefits, like portability and performance.
In response to Keyboard keyboard(&pokitto);
, some recommended reading: Most Vexing Parse.
And lastly, if you can replace modulo with an if-else, it’s usually worth doing - there’s no hardware divider on the Cortex-M0+ (nor the AVR chips for that matter).
And std::array
is better than a raw array because you get some bonus common operations and it’s easier to swap out with e.g. a std::vector
or a std::deque
later on if need be.
Plus, all its operations are pretty much as cheap as using a raw array anyway.