Pokitext: text editor on pokitto

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

At the moment I think:

  • C + Up and C + Down should be used to change page (i.e. back and forth instead of toggle)
  • B + Left should be ‘backspace’
    • The character is deleted when left is pressed so the user can hold B and tap left to keep deleting
  • B + Right should be ‘delete’
    • The character is deleted when right is pressed so the user can hold B and tap left to keep deleting

There are only two pages, so toggle is enough, we save one combo for something else.

Is there a way to find out what the FPS is set to? There is a setter but not a getter, and the attributes are private. I need it to compute the correct key repeat time.

Could you just calculate the time spend? Pokitto::Core::getTime() (in milliseconds).

Edit: You might know this already, but there is a very simple keyboard in Core::keyboard().

1 Like

I could, but I’d be implementing my own version of Buttons::repeat(…), which is already there, so first I’d like to see if there is a better way. @jonne would adding a getter to Pokittolib be a problem?

Core::keyboard(…) uses a fixed repeat values (even though it has access to the FPS value) – not good for an advanced keyboard.

Just make a pull request against PokittoLib.

1 Like