diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 0655dc8..930e804 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -113,6 +113,9 @@ true true + + false + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index aafb310..f6e61d1 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 f5d6ff5..63b0237 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 653e71d..189e772 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 0000000..0147243 --- /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 fe1761c..5a30c28 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 { @@ -21123,6 +21164,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)); @@ -21158,6 +21239,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++; + } + + + } } } @@ -21189,6 +21319,7 @@ namespace olc::hw // Swap active cache index nKeyCacheIndex ^= 0x01; + } } #define PGE_HW_KEYBOARD_IMPLEMENTED 1