From 8b7e1e73e68a2af63a7677973e4318d34252dc24 Mon Sep 17 00:00:00 2001
From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com>
Date: Sun, 18 Oct 2026 15:36:56 +0100
Subject: [PATCH] Text Entry Mode adapted from PGE2
---
.../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 5 +-
.../olcPGE3_BuildSH.vcxproj.filters | 3 +
dev/src/hw_keyboard.cpp | 90 +++++++++++
dev/src/hw_keyboard.h | 42 ++++++
examples/olcPGE3_TextEntry.cpp | 140 ++++++++++++++++++
olcPixelGameEngine3.h | 131 ++++++++++++++++
6 files changed, 410 insertions(+), 1 deletion(-)
create mode 100644 examples/olcPGE3_TextEntry.cpp
diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj
index 46c35035..45b6ddfa 100644
--- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj
+++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj
@@ -108,9 +108,12 @@
true
- false
+ true
false
+
+ false
+
true
true
diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters
index aafb3102..f6e61d15 100644
--- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters
+++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters
@@ -102,6 +102,9 @@
Source Files
+
+ Source Files
+
diff --git a/dev/src/hw_keyboard.cpp b/dev/src/hw_keyboard.cpp
index f5d6ff5d..63b0237b 100644
--- a/dev/src/hw_keyboard.cpp
+++ b/dev/src/hw_keyboard.cpp
@@ -187,6 +187,46 @@ namespace olc::hw
}
}
+ void Keyboard::EnableTextEntry(const std::string_view sDefault, const std::string_view delim, const bool bLeaveMode)
+ {
+ sTextEntryString = sDefault;
+ vecTextEntryCommands.clear();
+ sTextEntryDelim = delim;
+ nTextEntryCursorPos = uint32_t(sTextEntryString.size());
+ bTextEntryMode = true;
+ bLeaveTextEntryModeUponDelimiter = bLeaveMode;
+ }
+
+ void Keyboard::DisableTextEntry()
+ {
+ bTextEntryMode = false;
+ }
+
+ bool Keyboard::TextEntryEnabled() const
+ {
+ return bTextEntryMode;
+ }
+
+ const std::string& Keyboard::GetCurrentText() const
+ {
+ return sTextEntryString;
+ }
+
+ int32_t Keyboard::GetTextCursorPosition() const
+ {
+ return nTextEntryCursorPos;
+ }
+
+ const std::vector& Keyboard::GetTextCommands() const
+ {
+ return vecTextEntryCommands;
+ }
+
+ void Keyboard::ClearTextCommands()
+ {
+ vecTextEntryCommands.clear();
+ }
+
const Button& Keyboard::GetKey(const olc::Key key) const
{
return keys.at(size_t(key));
@@ -222,6 +262,55 @@ namespace olc::hw
if (state)
{
keyCache[nKeyCacheIndex].push_back(key);
+
+ // Process Text entry
+ if (bTextEntryMode)
+ {
+ const auto& sym = GetKeyGlyph(key, GetKey(olc::Key::SHIFT).bHeld, GetKey(olc::Key::CTRL).bHeld);
+
+
+ // Check for command characters
+ if (sym == "_L") // Left
+ nTextEntryCursorPos = std::max(0, nTextEntryCursorPos - 1);
+ else if (sym == "_R") // Right
+ nTextEntryCursorPos = std::min(int32_t(sTextEntryString.size()), nTextEntryCursorPos + 1);
+ else if (sym == "\b") // Backspace
+ {
+ // Still need 'back' to be consumed
+ if (nTextEntryCursorPos > 0)
+ {
+ sTextEntryString.erase(nTextEntryCursorPos - 1, 1);
+ nTextEntryCursorPos = std::max(0, nTextEntryCursorPos - 1);
+ }
+ }
+ else if (sym == "_X" && size_t(nTextEntryCursorPos) < sTextEntryString.size()) // Del
+ {
+ sTextEntryString.erase(nTextEntryCursorPos, 1);
+ }
+ /*else if (sym == "_U")
+ {
+
+ }
+ else if (sym == "_D")
+ {
+
+ }*/
+ else if (sym == sTextEntryDelim)
+ {
+ vecTextEntryCommands.push_back(sTextEntryString);
+ sTextEntryString.clear();
+ nTextEntryCursorPos = 0;
+ if(bLeaveTextEntryModeUponDelimiter)
+ DisableTextEntry();
+ }
+ else if (sym.size() == 1)
+ {
+ sTextEntryString.insert(nTextEntryCursorPos, sym);
+ nTextEntryCursorPos++;
+ }
+
+
+ }
}
}
@@ -253,6 +342,7 @@ namespace olc::hw
// Swap active cache index
nKeyCacheIndex ^= 0x01;
+
}
}
//! END IMPLEMENTATION
diff --git a/dev/src/hw_keyboard.h b/dev/src/hw_keyboard.h
index 653e71db..189e7729 100644
--- a/dev/src/hw_keyboard.h
+++ b/dev/src/hw_keyboard.h
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include
//! END STDHEADER
@@ -85,6 +86,40 @@ namespace olc
// Set keyboard layout for glyph mapping
void UseKeyboardLayout(const olc::KeyboardLayout kbl);
+ public: // Text Entry Interface
+
+ // Text is handled differently to keyboard input, but is fundamentally
+ // driven by keyboard input (or at least events that smell like a keyboard)
+ //
+ // To use, EnableTextEntry() which then starts to accumulate keyboard
+ // input from OS. A cursor is also maintained that can be moved with
+ // left and right arrow keys, and delete and backspace work as expected.
+ //
+ // When the user presses a delimiter key (e.g. enter), the current text
+ // input is added to a command buffer the user can interrogate. This
+ // method is used because some 3rd party device drivers may leverage
+ // keyboard input to do other interesting things like barcode readers.
+ //
+ // Being in Text entry mode does not prohibit normal keyboard behaviour
+ // so endure your application handles keyboard input appropriately.
+ //
+ // Leaving TextEntryMode happens automatically when delimiter encountered
+
+ // Enter Text Entry Mode
+ void EnableTextEntry(const std::string_view sDefault = "", const std::string_view delim = "\n", const bool bLeaveMode = true);
+ // Leave Text Entry Mode
+ void DisableTextEntry();
+ // Is TextEntryMode enabled?
+ bool TextEntryEnabled() const;
+ // Get the current accumulated text input
+ const std::string& GetCurrentText() const;
+ // Get the current cursor position within the input string
+ int32_t GetTextCursorPosition() const;
+ // Get a RO vector of current commands
+ const std::vector& GetTextCommands() const;
+ // User must clear accumulated commands when appropriate
+ void ClearTextCommands();
+
private:
// Set key state from system keycode
void SetKey(const olc::Key key, bool state);
@@ -104,6 +139,13 @@ namespace olc
std::array, 2> keyCache{};
size_t nKeyCacheIndex = 0;
+ bool bTextEntryMode = false;
+ std::string sTextEntryString = "";
+ int32_t nTextEntryCursorPos = 0;
+ std::vector vecTextEntryCommands;
+ std::string sTextEntryDelim = "\n";
+ bool bLeaveTextEntryModeUponDelimiter = true;
+
// Map of olc::Keycodes to printable glyphs
struct sKeyGlyph
{
diff --git a/examples/olcPGE3_TextEntry.cpp b/examples/olcPGE3_TextEntry.cpp
new file mode 100644
index 00000000..0147243d
--- /dev/null
+++ b/examples/olcPGE3_TextEntry.cpp
@@ -0,0 +1,140 @@
+/*
+ olc::PixelGameEngine3 Example - Text Entry Mode
+
+ Allows the user to enter and edit text
+
+ Licenced under the OLC-3 License
+*/
+
+
+// Define OLC_PGE3_APPLICATION to include the implementation of
+// the Pixel Game Engine as part of this translation unit
+#define OLC_PGE3_APPLICATION
+#include "../olcPixelGameEngine3.h"
+
+// Example application demonstrating text entry and editing. This class
+// overrides the olc::PixelGameEngine base class by implementing
+// the OnUserCreate() and OnUserUpdate() functions
+class Example_TextEntryMode : public olc::PixelGameEngine
+{
+public:
+ Example_TextEntryMode()
+ {
+ sAppName = "Example - Text Entry Mode";
+ }
+
+protected:
+ float fTickerTime = 0.0f;
+
+ // We'll draw random text in the background with
+ // a typing effect
+ std::list listHistory;
+
+public:
+ // Called once at the start, so create things here
+ bool OnUserCreate() override
+ {
+ listHistory.push_back("Welcome to Text Entry Demo!");
+ listHistory.push_back("");
+ listHistory.push_back("Press 'TAB' to enter text entry mode");
+ listHistory.push_back("Type your message");
+ listHistory.push_back("Move cursor with LEFT & RIGHT");
+ listHistory.push_back("Edit message with backspace and delete ");
+ listHistory.push_back("");
+ listHistory.push_back("Pressing 'ENTER' will force a 'command'");
+ listHistory.push_back("Give it a go!");
+ return true;
+ }
+
+ // Called every frame, so update things here
+ bool OnUserUpdate(float fElapsedTime) override
+ {
+ // Clear whole screen
+ draw.Clear(olc::Colour::VERY_DARK_BLUE);
+
+ // Check if commands from text entry mode exist
+ if (!keyboard.GetTextCommands().empty())
+ {
+ // There are commands waiting to be processed
+ for (const auto& sNewCommand : keyboard.GetTextCommands())
+ {
+ // capture them by adding to history queue
+ listHistory.push_back(sNewCommand);
+ if (listHistory.size() > 10)
+ listHistory.pop_front();
+ }
+
+ // Up to user to remove commands when appropriate
+ keyboard.ClearTextCommands();
+ }
+
+
+ // Draw Command History
+ olc::vf2d vPos = { 10,10 };
+ for (const auto& sCommand : listHistory)
+ {
+ draw.StringProp(vPos, sCommand, olc::Colour::CYAN);
+ vPos.y += 12;
+ }
+
+
+ if (keyboard.GetKey(olc::Key::TAB).bPressed)
+ {
+ if (keyboard.TextEntryEnabled())
+ {
+ keyboard.DisableTextEntry();
+ }
+ else
+ {
+ // Start text entry. In this instance we start with a
+ // blank input string, and we want to respond to the
+ // enter key being pressed. The false parameter tells
+ // the system not to leave text entry mode when a
+ // command has been entered, whcih for this demo is
+ // a lot more fluid.
+ keyboard.EnableTextEntry("", "\n", false);
+ }
+ }
+
+ if (keyboard.TextEntryEnabled())
+ {
+ // Read current text, and virtual cursor pos
+ const std::string& sText = keyboard.GetCurrentText();
+ const uint32_t nCursor = keyboard.GetTextCursorPosition();
+
+ // Work out where the cursor should be drawn
+ const auto vTextSizeToCursor = draw.GetTextSize(sText.substr(0, nCursor), true);
+
+ olc::vf2d vOffset = { 10,140 };
+
+ // Draw Cursor
+ draw.FilledRect(vOffset + olc::vf2d(0, -10) + vTextSizeToCursor, { 2, 10 }, olc::Colour::YELLOW);
+
+ // Draw Entered Text
+ draw.StringProp(vOffset, sText);
+
+ }
+
+
+ // Successful frame
+ return true;
+ }
+};
+
+
+// Main entry point for the application
+int main()
+{
+ // Construct demo application
+ Example_TextEntryMode demo;
+
+ // Create "screen" of 256x240 "pixels"
+ // with a pixel size of 4x4 actual screen pixels
+ if (demo.Construct({ 256, 240 }, { 4, 4 }))
+ {
+ // Start the application
+ demo.Start();
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h
index edd73b24..3672dc26 100644
--- a/olcPixelGameEngine3.h
+++ b/olcPixelGameEngine3.h
@@ -3949,6 +3949,40 @@ namespace olc
// Set keyboard layout for glyph mapping
void UseKeyboardLayout(const olc::KeyboardLayout kbl);
+ public: // Text Entry Interface
+
+ // Text is handled differently to keyboard input, but is fundamentally
+ // driven by keyboard input (or at least events that smell like a keyboard)
+ //
+ // To use, EnableTextEntry() which then starts to accumulate keyboard
+ // input from OS. A cursor is also maintained that can be moved with
+ // left and right arrow keys, and delete and backspace work as expected.
+ //
+ // When the user presses a delimiter key (e.g. enter), the current text
+ // input is added to a command buffer the user can interrogate. This
+ // method is used because some 3rd party device drivers may leverage
+ // keyboard input to do other interesting things like barcode readers.
+ //
+ // Being in Text entry mode does not prohibit normal keyboard behaviour
+ // so endure your application handles keyboard input appropriately.
+ //
+ // Leaving TextEntryMode happens automatically when delimiter encountered
+
+ // Enter Text Entry Mode
+ void EnableTextEntry(const std::string_view sDefault = "", const std::string_view delim = "\n", const bool bLeaveMode = true);
+ // Leave Text Entry Mode
+ void DisableTextEntry();
+ // Is TextEntryMode enabled?
+ bool TextEntryEnabled() const;
+ // Get the current accumulated text input
+ const std::string& GetCurrentText() const;
+ // Get the current cursor position within the input string
+ int32_t GetTextCursorPosition() const;
+ // Get a RO vector of current commands
+ const std::vector& GetTextCommands() const;
+ // User must clear accumulated commands when appropriate
+ void ClearTextCommands();
+
private:
// Set key state from system keycode
void SetKey(const olc::Key key, bool state);
@@ -3968,6 +4002,13 @@ namespace olc
std::array, 2> keyCache{};
size_t nKeyCacheIndex = 0;
+ bool bTextEntryMode = false;
+ std::string sTextEntryString = "";
+ int32_t nTextEntryCursorPos = 0;
+ std::vector vecTextEntryCommands;
+ std::string sTextEntryDelim = "\n";
+ bool bLeaveTextEntryModeUponDelimiter = true;
+
// Map of olc::Keycodes to printable glyphs
struct sKeyGlyph
{
@@ -21102,6 +21143,46 @@ namespace olc::hw
}
}
+ void Keyboard::EnableTextEntry(const std::string_view sDefault, const std::string_view delim, const bool bLeaveMode)
+ {
+ sTextEntryString = sDefault;
+ vecTextEntryCommands.clear();
+ sTextEntryDelim = delim;
+ nTextEntryCursorPos = uint32_t(sTextEntryString.size());
+ bTextEntryMode = true;
+ bLeaveTextEntryModeUponDelimiter = bLeaveMode;
+ }
+
+ void Keyboard::DisableTextEntry()
+ {
+ bTextEntryMode = false;
+ }
+
+ bool Keyboard::TextEntryEnabled() const
+ {
+ return bTextEntryMode;
+ }
+
+ const std::string& Keyboard::GetCurrentText() const
+ {
+ return sTextEntryString;
+ }
+
+ int32_t Keyboard::GetTextCursorPosition() const
+ {
+ return nTextEntryCursorPos;
+ }
+
+ const std::vector& Keyboard::GetTextCommands() const
+ {
+ return vecTextEntryCommands;
+ }
+
+ void Keyboard::ClearTextCommands()
+ {
+ vecTextEntryCommands.clear();
+ }
+
const Button& Keyboard::GetKey(const olc::Key key) const
{
return keys.at(size_t(key));
@@ -21137,6 +21218,55 @@ namespace olc::hw
if (state)
{
keyCache[nKeyCacheIndex].push_back(key);
+
+ // Process Text entry
+ if (bTextEntryMode)
+ {
+ const auto& sym = GetKeyGlyph(key, GetKey(olc::Key::SHIFT).bHeld, GetKey(olc::Key::CTRL).bHeld);
+
+
+ // Check for command characters
+ if (sym == "_L") // Left
+ nTextEntryCursorPos = std::max(0, nTextEntryCursorPos - 1);
+ else if (sym == "_R") // Right
+ nTextEntryCursorPos = std::min(int32_t(sTextEntryString.size()), nTextEntryCursorPos + 1);
+ else if (sym == "\b") // Backspace
+ {
+ // Still need 'back' to be consumed
+ if (nTextEntryCursorPos > 0)
+ {
+ sTextEntryString.erase(nTextEntryCursorPos - 1, 1);
+ nTextEntryCursorPos = std::max(0, nTextEntryCursorPos - 1);
+ }
+ }
+ else if (sym == "_X" && size_t(nTextEntryCursorPos) < sTextEntryString.size()) // Del
+ {
+ sTextEntryString.erase(nTextEntryCursorPos, 1);
+ }
+ /*else if (sym == "_U")
+ {
+
+ }
+ else if (sym == "_D")
+ {
+
+ }*/
+ else if (sym == sTextEntryDelim)
+ {
+ vecTextEntryCommands.push_back(sTextEntryString);
+ sTextEntryString.clear();
+ nTextEntryCursorPos = 0;
+ if(bLeaveTextEntryModeUponDelimiter)
+ DisableTextEntry();
+ }
+ else if (sym.size() == 1)
+ {
+ sTextEntryString.insert(nTextEntryCursorPos, sym);
+ nTextEntryCursorPos++;
+ }
+
+
+ }
}
}
@@ -21168,6 +21298,7 @@ namespace olc::hw
// Swap active cache index
nKeyCacheIndex ^= 0x01;
+
}
}
#define PGE_HW_KEYBOARD_IMPLEMENTED 1