Pokitext: text editor on pokitto

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.

This is my preliminary idea for a command interface.
It’s probably still flawed, but I think it’s going to be easier to maintain and work with:

#include <cstdint>

enum class KeyboardCommandType : std::uint8_t
{
	// Does nothing
	None = 0x00,
	
	// Character
	Char = 0x01,
	
	// Arrow keys
	ArrowUp = 0x10,
	ArrowDown = 0x11,
	ArrowLeft = 0x12,
	ArrowRight = 0x13,	
	
	Backspace = 0x14,
	Enter = 0x15,
	
	Tab = 0x16,
	CapsLock = 0x17
	
	Control = 0x18,
	Alt = 0x19,
	AltGr = 0x1A,
	
	Escape = 0x1F,
	
	// Grouping of 6
	Insert = 0x20,
	Delete = 0x21,
	Home = 0x22,
	End = 0x23,
	PageUp = 0x24,
	PageDown = 0x25,
	
	// Function keys
	F1 = 0xF1,
	F2 = 0xF2,
	F3 = 0xF3,
	F4 = 0xF4,
	F5 = 0xF5,
	F6 = 0xF6,
	F7 = 0xF7,
	F8 = 0xF8,
	F9 = 0xF9,
	F10 = 0xFA,
	F11 = 0xFB,
	F12 = 0xFC,
}

struct KeyboardCommand
{
	KeyboardCommandType type = KeyboardCommandType::None;
	char32_t character = '\0';
	
	constexpr KeyboardCommand(void) = default;
	
	constexpr KeyboardCommand(KeyboardCommandType type)
		: type(type), character('\0')
	{
	}
	
	constexpr KeyboardCommand(char32_t chararacter)
		: type(KeyboardCommandType::InsertChar), character(character)
	{
	}
	
	constexpr KeyboardCommand(KeyboardCommandType type, char32_t chararacter)
		: type(type), character(character)
	{
	}
};

class CommandInputStream
{
public:
	virtual std::size_t getCommandsPending(void) const = 0;
	
	virtual KeyboardCommand peekNextCommand(void) const = 0;
	virtual KeyboardCommand readNextCommand(void) = 0;
	
	virtual bool hasCommandsPending(void) const
	{
		return (this->getCommandsPending > 0);
	}
};

class CommandOutputStream
{
public:
	virtual std::size_t getCommandsPending(void) const = 0;
	
	virtual void emitCommand(KeyboardCommand command) = 0;
	
	virtual void flush(void)
	{
	}
	
	virtual bool hasCommandsPending(void) const
	{
		return (this->getCommandsPending > 0);
	}
};

By representing a keystroke as a command type and a character it allows the entire unicode range to be used, thus giving the keyboard flexibility, but also allows common command keys to be used.

At the moment I’m thinking that for Control, the command won’t be queued until the keyboard side detects that the user has pressed another key whilst holding control, after which it either submits Control as the type and the other key pressed as the character, or submits two commands in a row.
Either way, this allows keyboard shortcuts to be used.

I was thinking that instead of a ‘switch page’ command, there could be a number of pages and the F1-F12 keys could be used for selecting pages.
Either that or set up a shortcut like Control+'<' for ‘previous page’ and Control+'>' for ‘next page’.

The important thing about this system is that rather than separating the implementation into ‘keyboard input’ and ‘screen output’, it’s establishing the notion of commands and command streams, which is more abstract and thus a bit more flexible.
Instead of just the keyboard and the screen, you can now have the keyboard, a keyboard input processor to convert keystrokes to commands and a command reader that takes the keyboard commands and uses them to manipulate the text field.

Ideally it would be good to not have to tie the KeyboardCommandType to keyboard keys, but I found that the alternative results in making assumptions about what the text editor is capable of, which results in less flexibility.

I’ll post the result of that approach in a details block:

Other approach
#include <cstdint>

enum class KeyboardCommandType : std::uint8_t
{
	//
	// 0x0N - General
	//
	
	// Does nothing
	None = 0x00,
	
	//
	// 0x1N - Insert and Delete
	//	
	
	// Insert character at cursor position
	InsertForwards = 0x10,
	
	// Insert character before cursor position
	InsertBackwards = 0x11,
	
	// Overwrite character at cursor position
	OverwriteForwards = 0x12,
	
	// Overwrite character before cursor position
	OverwriteBackwards = 0x13,
	
	// Delete the character at cursor, i.e. delete
	DeleteForwards = 0x14,
	
	// Delete the character before cursor, i.e. backspace
	DeleteBackwards = 0x15,

	//
	// 0x2N - Navigation
	//
	
	// To allow menu navigation?
	Enter = 0x20,
	Cancel = 0x21,
	
	// Move the cursor
	Up = 0x22,
	RightUp = 0x23,
	Right = 0x24,
	LeftDown = 0x25,
	Down = 0x26,
	RightDown = 0x27,
	Left = 0x28,
	LeftUp = 0x29,
		
	// Function as 'Home' and 'End' keys
	LineStart = 0x2A,
	LineEnd = 0x2B,
	
	// Move the cursor to the start of the previous word
	PreviousWord = 0x2C,
	
	// Move the cursor to the end of the next word
	NextWord = 0x2D,
	
	//
	// 0x3N - Select, copy, cut and paste
	//
	
	// To begin selecting text or to cancel selecting text
	BeginSelect = 0x30,
	EndSelect = 0x31,
	
	// Standard
	Copy = 0x32,
	Cut = 0x33,
	Paste = 0x34,
	
	//
	// 0x4N - Standard document operations
	//
	
	Save = 0x40,
	Undo = 0x41,
	Redo = 0x42,
	
	// Find
	
	Find = 0x50,
	FindNext = 0x51,
	FindPrevious = 0x52,
	ReplaceCurrent = 0x53,
}

Tomorrow I’ll look at getting something on the screen that can use both ends of this.

1 Like

Looks good, I just think it’s getting too flexible – at first I was thinking I’d create a general, Pokitto-independent onscreen keyboard library, with many layers of abstraction as well, and then I’d make a Pokitto frontend and so on – but I don’t think it’s worth it. My impression is that we’re having diverging visions – I don’t know if I want to even support Unicode for the keyboard (the editor can still support it) – which isn’t necessarily bad, we can try both and then see what happens. I’ll probably steal some of your ideas, but will simplify it a lot. In the same way, feel free to fork that code I posted (consider it CC0).

There aren’t that many layers involved.

The keyboard is somehow hooked up as a CommandOutputStream which spits out commands, then the thing controlling the text editor is set up to read commands out of that and process them however it pleases.

The keyboard system can either be:

  • An on-screen system that directly implements CommandOutputStream
    • On screen keyboard (generates commands directly) -> text controller
  • A bluetooth keyboard with a middleman layer that implements CommandOutputStream and converts the hardware keycodes to KeyboardCommands
    • Keyboard -> keycode to command converter -> text controller
  • A UART/USART device with a deserialisation/middleman layer
    • Serial -> serial to command converter -> text controller
  • A file filled with serialised commands?
    • File -> deserialising command generator -> text controller

It’s all just streams. Like Linux’s console - every program is a stream.

You don’t have to. You can convert a char (i.e. an ‘ascii character’) to and from a char32_t (i.e. a ‘UTF32 character’) without issue.

But by having a char32_t in the interface, it would allow someone who does want UTF32 to be able to use UTF32 without stressing about how to pack a char32_t into a char shaped hole.

And if you’re worried about RAM consumption, just use these:

struct KeyboardCommandUtf16
{
	KeyboardCommandType type = KeyboardCommandType::None;
	char16_t character = '\0';
	
	constexpr KeyboardCommandUtf16(void) = default;
	
	constexpr KeyboardCommandUtf16(KeyboardCommandType type)
		: type(type), character('\0')
	{
	}
	
	constexpr KeyboardCommandUtf16(char16_t chararacter)
		: type(KeyboardCommandType::InsertChar), character(character)
	{
	}
	
	constexpr KeyboardCommandUtf16(KeyboardCommandType type, char16_t chararacter)
		: type(type), character(character)
	{
	}
	
	constexpr explicit operator KeyboardCommand(void) const
	{
		return KeyboardCommand(this->type, this->character);
	}
};

struct KeyboardCommandAscii
{
	KeyboardCommandType type = KeyboardCommandType::None;
	char character = '\0';
	
	constexpr KeyboardCommandAscii(void) = default;
	
	constexpr KeyboardCommandAscii(KeyboardCommandType type)
		: type(type), character('\0')
	{
	}
	
	constexpr KeyboardCommandAscii(char chararacter)
		: type(KeyboardCommandType::InsertChar), character(character)
	{
	}
	
	constexpr KeyboardCommandAscii(KeyboardCommandType type, char chararacter)
		: type(type), character(character)
	{
	}
	
	constexpr explicit operator KeyboardCommand(void) const
	{
		return KeyboardCommand(this->type, this->character);
	}
};
1 Like

Hmmm I see, the stream is just an interface to be implemented by the keyboard? I’m not against that idea, I’d just like to have a dead simple keyboard with minimal interface like in my code… and then maybe make a thin wrapper that would offer the stream interface and more fancy stuff. I’ll have to look more into it, I just skimmed over as I am on mobile. I’ll get to my laptop in a while.

The keyboard and anything else that makes sense.

What would that look like, if not a stream of commands?

You press a key, a command is generated,
that command is fed to the editor, the editor acts upon that command.
This is practically the embodiment of minimalism.

For example the method hasCommandsPending is something I’d like to avoid to keep the number of methods as low as possible. Or the command being a struct – I’d like to keep it simply an enum for readability and simplicity. Enum is what corresponds to the keyboard keys well enough, keys have no parameters (on a real keyboard they could have modifiers like CTRL or something, but not on my simple keyboard, that’s unnecessary).

Why? ‘Methods’ are no more complex than free functions.

Without virtual functions you can’t swap out implementations at runtime without resorting to using function pointers, which are much harder to use.

A struct is no less readable than an enum.

You’ll have to convert the enum to a char at some point anyway, so by avoiding a struct you’re just offsetting the complexity to somewhere else (which I suspect will take the form of a lookup table).

I’ve made a very basic version that illustrates the concept, but is a long way off being done.

Here’s the release:

And here’s the source:

I’ve MIT licenced it so people don’t have to worry about the ‘state changes’ clause of Apache 2.0.

1 Like

I am going to check your work out :slight_smile: I haven’t cleaned my code very much yet, I rather focused on making the graphics nicer so that we can experiment with different layouts and ideas etc.

out

I’ll make a repo as well so that you and others can do this forking easily.

EDIT:

here

and here is a bin

firmware.bin (54.5 KB)

1 Like

Try to incorporate at least one of those changes I suggested.

(I’d like to point out that binary literals are a C++14 thing and C doesn’t even have binary literals,
just in case you weren’t aware.)

2 Likes

That’d be awesome to make a Typing of the Dead with such a keyboard

Alllright, I’m on it right now :slight_smile:


What should the B button be used for to make the typing faster? What comes in mind:

  • page switch
  • backspace
  • cursor actions
  • some kind of quick jumps, e.g. to the edge of the keyboard

We could do multiple of these via B + other button combos.

Should C button be used as well? If we use it, the programmer will have no free buttons to e.g. hide the keyboard and will have to handle this in a more complicated way.

2 Likes

Seeing how often we might make typos, I think that B as backspace should be quite important. And I think that’s what most people are used to anyway? Maybe it’s just me.

Yeah, backspace will be frequent… maybe like this?

  • B+A = backspace
  • B+arrows = cursor movement (including up/down in multiline editors)

I’m now thinking maybe we don’t have to leave C free. It could be used in some combos (except C + left/right, which is volume), and one of these (like C+down) could mean close the keyboard, which the keyboard would indicate via its interface. But there should still be a button for closing the keyboard as well, not only combo, because that’s unintuitive and new users wouldn’t know how to close the keyboard (we certainly don’t want to follow the vim meme). Actually the enter key could mean close the keyboard/confirm in one line editors, but in multiple line editors that’s just new line.

It’s getting so complicated :exploding_head: Have to think about it for a while.

EDIT:

Basically, the keyboard should only be a board with buttons, some of which have hotkey combos, but it shouldn’t have it’s own logic, in the sense of knowing what these buttons mean. It simply lets the user push buttons and sends these to the program. The program should be the one who knows what the keys mean and should handle the logic.

There should be no combos that generate an action without there also being a graphical button that can be pressed without using the combo.

So, we need a keyboard handler (@Pharap mentioned it somewhere above I think). More than one, at least a single line editor handler (e.g. for popup windows for short answers in games) and a multiline editor handler. These should probably be part of the keyboard module, because you’ll rarely use the keyboard without them.

I need to think about the exact responsibilities these handlers should have.

1 Like