Pokitext: text editor on pokitto

It depends on the audience really, I think anything else than a keyboard will scare people away except for very few, like you and me maybe… and since we’re probably the minority, my feeling is we should leave the intuitive option as default and modify our personal versions to whatever we like. But that’s just my feeling, let’s try both and see how it feels, okay?

In that case the wheel needs to be shrink down, it’s taking too much space as it is, too many lonely pixels :smile:

EDIT: BTW this is a working prototype? If so, don’t be shy to upload :smile:

1 Like

:laughing:

No, it’s just a really quick mock-up.

2 Likes

Oh my… You guys are making me remember those days I was using my Dreamcast to chat on IRC using the control pad and a virtual keyboard. You’d be surprised how fast I got using just a controller and the virtual keyboard

2 Likes

In the spirit of “really effective and difficult to learn”… check out plover. I wonder if something like that could be made to work on a Pokitto.

1 Like

Interesting, reminds me of that program that flashed words real fast to make you read quickly as hell.


Trying to mock some GUI:

image

This is 110x88. Looking at it, it really seems like the arrows are too far away to reach most of the time, considering they’d be used frequently… we should probably make hotkeys for cursor movement.

4 Likes

Will you be providing the C++ compiler also? :star_struck:

3 Likes

I want that keyboard if we port Zork to the pokitto. Just add a Zork theme to it and we are good for adventures lol

2 Likes

out

With fast key repeats the typing feels relatively quick – this is already as fast as my grandma on PC, which is an okay start – I bet it will get a lot better with a bit of a training. I typed HELLO WORK because the keys sometimes react weirdly, don’t know if it’s caused by the PC or something, this will have to be fine tuned.

1 Like

It was added in either 5.2 or 5.3.

I was thinking something lower powered.
It could possibly have an external power source even.

Either that or create a USB or PS/2 hat with maybe a small AVR chip to do any extra processing.

Silly, but also fun and relatively cheap.

I’d say the SD card rather than EEPROM.

(Although to be honest I think we should be reserving part of the EEPROM for Pokitto-specific settings anyway, and encouraging people to save games to the SD card.)

Perhaps. The 3DS does something like that.
The OS implements the keyboard and games do a system call to get access to the OS keyboard.
But the circumstances are slightly different there - there are two CPU cores and one is dedicated to the OS, which allows the OS to suspend the other core.

No, but the most you ever have to type is about 10 characters.
(The only games I remember using that sort of thing were Pokemon and Zelda - for name entry and nothing else.)
We’re probably talking paragraphs here, so I think that would get a bit awkard.

I was thinking runtime, not compile time.
Use a std::map<KeyIndex, KeyCode> and save/load the contents to/from a configuration file on the SD card.
That would allow maximum flexibility for minimum effort.

Just reuse the default one for now.

This is the sort of thing I meant when refering to a “wheel-based thing” earlier:

Also +1 for rickrolling us.

I think it’s easier to get to grips with than vim.
Part of the problem with vim is that you have to remember all the commands.
In this case, there’s a visual aid, so it should be quicker for people to pick up.

I don’t think we should use it ‘instead of’ the other option, but I think it’s worth trying to include both.

This demonstrates my website gripes again - no image showing what the software actually looks like.

That said, I’ve always wanted to learn shorthand.

That’s what subbed anime is for.

If it weren’t for the hardcoded sound control, I’d say C + arrow to quickly switch to arrow navigation.

*resists urge to complain about problems with the C++ code*

1 Like

The presentation does a much better job than images.

trying out an version of scroll wheel type thing, not sure if its clear what im trying to do, but if this will end up as library stuff i was looking at making it compact with this index thing, idk if it makes sence or if it works

#include <string>
#include "Pokitto.h"

Pokitto::Core editor;
char keyboard[] = "abcdefghijklnopqrstuvwxyz .";
int main () {
    editor.begin();
    editor.display.setFont(font3x5);
    
    std::string text = "";
    int cursor = 0;
    
    while (editor.isRunning()) {

        if (editor.update()) {
            editor.display.print(text.c_str());
            
            
            int index =0;
            if(editor.buttons.upBtn()){
                index +=1;
            }
            if(editor.buttons.downBtn()){
                index +=2;
            }
            if(editor.buttons.leftBtn()){
                index +=4;
            }
            if(editor.buttons.rightBtn()){
                index +=8;
            }
            index *= 3;
            if(editor.buttons.aBtn()){
                index +=1;
            }
            if(editor.buttons.bBtn()){
                index +=2;
            }
            if(editor.buttons.cBtn()){
                // dedicated menu button
            }
            editor.display.print(index);
            editor.display.print(keyboard[index]);
            
        }
    }

}
1 Like

I’ve started writing some code as well. I don’t wanna create a repo yet, let me just paste it here so that you can take a look and play around. Tomorrow I’ll give it a better shape.

Summary
#include "Pokitto.h"

Pokitto::Core pokitto;

enum Action: uint8_t
{
  ACTION_UPPER_A,
  ACTION_UPPER_B,
  ACTION_UPPER_C,
  ACTION_UPPER_D,
  ACTION_UPPER_E,
  ACTION_UPPER_F,
  ACTION_UPPER_G,
  ACTION_UPPER_H,
  ACTION_UPPER_I,
  ACTION_UPPER_J,
  ACTION_UPPER_K,
  ACTION_UPPER_L,
  ACTION_UPPER_M,
  ACTION_UPPER_N,
  ACTION_UPPER_O,
  ACTION_UPPER_P,
  ACTION_UPPER_Q,
  ACTION_UPPER_R,
  ACTION_UPPER_S,
  ACTION_UPPER_T,
  ACTION_UPPER_U,
  ACTION_UPPER_V,
  ACTION_UPPER_W,
  ACTION_UPPER_X,
  ACTION_UPPER_Y,
  ACTION_UPPER_Z,
  ACTION_LOWER_A,
  ACTION_LOWER_B,
  ACTION_LOWER_C,
  ACTION_LOWER_D,
  ACTION_LOWER_E,
  ACTION_LOWER_F,
  ACTION_LOWER_G,
  ACTION_LOWER_H,
  ACTION_LOWER_I,
  ACTION_LOWER_J,
  ACTION_LOWER_K,
  ACTION_LOWER_L,
  ACTION_LOWER_M,
  ACTION_LOWER_N,
  ACTION_LOWER_O,
  ACTION_LOWER_P,
  ACTION_LOWER_Q,
  ACTION_LOWER_R,
  ACTION_LOWER_S,
  ACTION_LOWER_T,
  ACTION_LOWER_U,
  ACTION_LOWER_V,
  ACTION_LOWER_W,
  ACTION_LOWER_X,
  ACTION_LOWER_Y,
  ACTION_LOWER_Z,
  ACTION_0,
  ACTION_1,
  ACTION_2,
  ACTION_3,
  ACTION_4,
  ACTION_5,
  ACTION_6,
  ACTION_7,
  ACTION_8,
  ACTION_9,
  ACTION_PERIOD,
  ACTION_COMMA,
  ACTION_SEMICOLON,
  ACTION_COLON,
  ACTION_EXCLAMATION_MARK,
  ACTION_QUESTION_MARK,
  ACTION_QUOTES,
  ACTION_APOSTROPHE,
  ACTION_SINGLE_QUOTE,
  ACTION_ROOF,
  ACTION_BRACKET_LEFT_ROUND,
  ACTION_BRACKET_RIGHT_ROUND,
  ACTION_BRACKET_LEFT_SQUARE,
  ACTION_BRACKET_RIGHT_SQUARE,
  ACTION_BRACKET_LEFT_CURLY,
  ACTION_BRACKET_RIGHT_CURLY,
  ACTION_LESS_THAN,
  ACTION_GREATER_THAN,
  ACTION_EQUALS,
  ACTION_MINUS,
  ACTION_PLUS,
  ACTION_ASTERISK,
  ACTION_TILDE,
  ACTION_SLASH,
  ACTION_BACK_SLASH,
  ACTION_UNDERSCORE,
  ACTION_PIPE,
  ACTION_SHARP,
  ACTION_DOLLAR,
  ACTION_PERCENT,
  ACTION_AMPERSAND,
  ACTION_AT,
  ACTION_SPACE,
  ACTION_TAB,
  ACTION_NEW_LINE,

  _ACTION_SPECIAL,

  ACTION_BACKSPACE,
  ACTION_DELETE,
  ACTION_CANCEL,
  ACTION_SHIFT,
  ACTION_CONTROL,
  ACTION_ARROW_LEFT,
  ACTION_ARROW_RIGHT,
  ACTION_ARROW_UP,
  ACTION_ARROW_DOWN,
  ACTION_SWITCH_PAGE,
  ACTION_NONE
};

const char actionChars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,;:!?\"'`^()[]{}<>=-+*~/\\_|#$%&@ \t\n ";

#define A(x) ACTION_##x
#define AU(x) ACTION_UPPER_##x
#define AL(x) ACTION_LOWER_##x

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
};

const Action keyboardLayoutPage1[] =
{
  A(CANCEL),  A(0),  A(1),  A(2),  A(3),  A(4),  A(5),  A(6),     A(7),          A(8),           A(9),          A(BACKSPACE),  A(DELETE),      A(LESS_THAN), A(GREATER_THAN),
  A(TAB),     AU(A), AU(B), AU(C), AU(D), AU(E), AU(F), AU(G),    AU(H),         AU(I),          AU(J),         A(NEW_LINE),   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(ARROW_UP),   A(AT),          A(EQUALS),    A(ASTERISK),
  A(CONTROL), AU(U), AU(V), AU(W), AU(X), AU(Y), AU(Z), A(SPACE), A(UNDERSCORE), A(SWITCH_PAGE), A(ARROW_LEFT), A(ARROW_DOWN), A(ARROW_RIGHT), A(SLASH),     A(BACK_SLASH)
};

const Action keyboardLayoutPage2[] =
{
  A(CANCEL),  A(PERIOD), A(COMMA), A(SEMICOLON), A(COLON), A(EXCLAMATION_MARK), A(QUESTION_MARK), A(QUOTES), A(APOSTROPHE), A(SINGLE_QUOTE), A(ROOF),       A(BACKSPACE),  A(DELETE),      A(BRACKET_LEFT_ROUND),  A(BRACKET_RIGHT_ROUND),
  A(TAB),     AL(A),     AL(B),    AL(C),        AL(D),    AL(E),               AL(F),            AL(G),     AL(H),         AL(I),           AL(J),         A(NEW_LINE),   A(PERCENT),     A(BRACKET_LEFT_SQUARE), A(BRACKET_RIGHT_SQUARE),
  A(SHIFT),   AL(K),     AL(L),    AL(M),        AL(N),    AL(O),               AL(P),            AL(Q),     AL(R),         AL(S),           AL(T),         A(ARROW_UP),   A(DOLLAR),      A(BRACKET_LEFT_CURLY),  A(BRACKET_RIGHT_CURLY),
  A(CONTROL), AL(U),     AL(V),    AL(W),        AL(X),    AL(Y),               AL(Z),            A(SPACE),  A(SHARP),      A(SWITCH_PAGE),  A(ARROW_LEFT), A(ARROW_DOWN), A(ARROW_RIGHT), A(TILDE),               A(PIPE)
};

#undef A
#undef AU
#undef AL

class Keyboard
{
protected:
  Pokitto::Core *pokitto;
  uint16_t       cursorX;
  uint16_t       cursorY;
  Action         currentAction;
  const Action  *pageLayout;

  inline bool buttonTriggered(int16_t button)
  {
    return this->pokitto->buttons.pressed(button) || this->pokitto->buttons.repeat(button,2);
  }

public:
  const static int16_t KEYS_X = 15;
  const static int16_t KEYS_Y = 4;
  const static int16_t KEY_SIZE = 6;

  Keyboard(Pokitto::Core *pokitto)
  {
    this->pokitto = pokitto;
    this->cursorX = 0;
    this->cursorY = 0;
    this->currentAction = ACTION_NONE;
    this->pageLayout = keyboardLayoutPage1;
  }

  Action getAction()
  {
    return this->currentAction;
  }

  void update()
  {
    this->currentAction = ACTION_NONE;

    if (this->buttonTriggered(BTN_RIGHT))
      this->cursorX = (this->cursorX + 1) % Keyboard::KEYS_X; 
    else if (this->buttonTriggered(BTN_LEFT))
      this->cursorX = this->cursorX > 0 ? this->cursorX - 1 : Keyboard::KEYS_X - 1;

    if (this->buttonTriggered(BTN_DOWN))
      this->cursorY = (this->cursorY + 1) % Keyboard::KEYS_Y; 
    else if (this->buttonTriggered(BTN_UP))
      this->cursorY = this->cursorY > 0 ? this->cursorY - 1 : Keyboard::KEYS_Y - 1; 

    if (this->pokitto->buttons.pressed(BTN_A))
    {
      this->currentAction = this->pageLayout[this->cursorY * Keyboard::KEYS_X + this->cursorX];
 
      if (this->currentAction == ACTION_SWITCH_PAGE)
      {
        this->pageLayout = this->pageLayout == keyboardLayoutPage1 ? keyboardLayoutPage2 : keyboardLayoutPage1;
        this->currentAction = ACTION_NONE;
      }
    }
  }

  void draw(int16_t x, int16_t y)
  {
    this->pokitto->display.setFont(fontTiny);

    for (int16_t keyY = 0; keyY < Keyboard::KEYS_Y; ++keyY)
    {
      int16_t drawY = y + keyY * (Keyboard::KEY_SIZE + 1);

      for (int16_t keyX = 0; keyX < Keyboard::KEYS_X; ++keyX)
      {
        int16_t drawX = x + keyX * (Keyboard::KEY_SIZE + 1);

        Action action = this->pageLayout[keyY * Keyboard::KEYS_X + keyX];
        
        if (action <= _ACTION_SPECIAL)
        {
          this->pokitto->display.setCursor(drawX, drawY);
          this->pokitto->display.print(actionChars[action]);
        }
        else
        {
          this->pokitto->display.drawMonoBitmap(drawX,drawY,actionIcons,action - _ACTION_SPECIAL - 1);
        }

        if (keyX == this->cursorX && keyY == this->cursorY)
          this->pokitto->display.drawRect(drawX,drawY,Keyboard::KEY_SIZE,Keyboard::KEY_SIZE);
      }
    }
  }
};

int main()
{
  pokitto.begin();

  char buffer[17];
  size_t pos = 0;

  for (uint8_t i = 0; i < 17; ++i)
    buffer[i] = 0;

  Keyboard keyboard(&pokitto);

  while (pokitto.isRunning())
  {
    if (pokitto.update())
    {
      keyboard.update();

      Action action = keyboard.getAction();
 
      if (action != ACTION_NONE)
      {
        if (action < _ACTION_SPECIAL)
        {
          buffer[pos] = actionChars[action];
          pos = (pos + 1) % 16;
        }
        else
        {
          switch (action)
          {
            case ACTION_BACKSPACE:
              pos = pos > 0 ? pos - 1 : 15;
              buffer[pos] = ' ';
              break;

            default: break;
          }
        }
      }

      keyboard.draw(2,5);
      pokitto.display.setCursor(1,50);
      pokitto.display.print(buffer);
    }
  }

  return 0;
}
1 Like

I don’t like the idea of ‘PAOEU’ mapping to ‘pie’.
It makes katakana look easy パイソン! (pa i so n) :P

Also English isn’t a phonetic language, it’s full of homophones (e.g. pseudo and sudo), so it’s awkward in that respect as well.

I like the idea of chords though.

For future reference, that should be \include <Pokitto.h>.
Quotes search your local files and then use the system library path as a fallback, angle brackets go straight to the system library path.

https://en.cppreference.com/w/cpp/preprocessor/include

Am I still the only person who doesn’t bother making a Core object?

I’ll be honest, I’m not entirely sure how this is supposed to work.

Aside from anything else, the index isn’t retained, and I think upBtn etc actually equates to ‘just pressed’ not ‘is currently presed’, so this won’t work quite how you’re expecting it to.

Do I have permission to dissect/review this?

1 Like

Definitely, it’s uncommented and dirty, but I’ll appreciate if you can tidy it :slight_smile: I’d say even go on and improve it but I dunno if we would agree on a license of our mixed code? I’m tempted to CC0 it again :smile:

1 Like

I don’t think it’s worth chucking a licence on stuff we’re just ‘spitballing’ in an open forum, so may as well just say it’s CC0 for the sake of argument.

By the way, what’s a ‘roof’? Do you mean a caret (^)?
And I presume by ‘pipe’ you mean a bar (|)?

1 Like

Yes, OMG I left that in :joy: I meant to correct these names.

EDIT:

I seriously didn’t know how caret was spelled, I’ve only ever heard it pronounced and thought it was “carrot” :joy: But I wasn’t sure and was lazy to Google so I went with roof.

1 Like

I’ve got to dash off, but I’ll do more later.
So far I’ve just turned the enum into a scoped enum and renamed some of the symbols.

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
};

Two points:

  • What is the difference between a single quote and an apostrophe? Usually keyboards only have ', which is arguably both and neither
  • Should ‘new line’ be ‘enter’ instead?

Also I’m still thinking perhaps something like:

struct Command
{
	CommandType type;
	char32_t character;
};

Might be a better option/more flexible since it will allow a keyboard to print practially any unicode character (i.e. we won’t have to keep adding more entries to the enum).

Programmers need to know these things dammit, you’re letting the side down :P

1 Like

its prety easy and its kinda working the idea is like a 3x3 pad with 3 sub buttons

press B gets you first, B gets you second and AB gets your 3rd
(since its a combo thing i havent figured out how to do that corectly, maybe wen you release the buttons it adds it to the string)

i know the numbers with index are wierd but its to try and prevent overlap, instead of if or switch casing the hole thing its solved with simple addition

now this only gives 27 character but if you would press the c button could toggle between a couple more “keypads” like navigation and numbers/special characters

#include <string>
#include <Pokitto.h>

Pokitto::Core editor;
char keyboard[][27] = {"abcdefghijklnopqrstuvwxyz ","ABCDEFGHIJKLNOPQRSTUVWXYZ "};
int main () {
    editor.begin();
    editor.display.setFont(font3x5);
    
    std::string text = "";
    int cursor = 0;
    int index =0;
    int page =0;
    bool pressed = false;
    while (editor.isRunning()) {

        if (editor.update()) {
            editor.display.print(text.c_str());
            
            if( editor.buttons.upBtn()    == false && 
                editor.buttons.downBtn()  == false &&
                editor.buttons.leftBtn()  == false &&
                editor.buttons.rightBtn() == false ){
                    if(pressed){
                        text += keyboard[page][index];
                        pressed = false;
                    }
                    
            }
            index =0;
            if(editor.buttons.upBtn()){
                index +=1;
            }
            if(editor.buttons.downBtn()){
                index +=2;
            }
            if(editor.buttons.leftBtn()){
                index +=4;
            }
            if(editor.buttons.rightBtn()){
                index +=8;
            }
            index *= 3;
            if(editor.buttons.aBtn()){
                index +=1;
                pressed = true;
            }
            if(editor.buttons.bBtn()){
                index +=2;
                pressed = true;
            }
            if(editor.buttons.cBtn()){
                // change page
                if(page == 0) page =1;
                else page =0;
            }
            editor.display.print(keyboard[page][index]);
            
        }
    }

}

sorry code is still not fully working, im not sure how to do multipress input like @FManga concept would be implemented

my alternative would to scroll between them with a and b

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.