CEGUI In Practice - Auto Repeat Key
Written for CEGUI 0.8
Works with versions 0.8.x (stable)
Works with latest CEGUI stable!
CEGUI In Practice series
Contents
Auto Key
CEGUI In Practice 8
This tutorial will build upon a previous tutorial we looked at, Managing Input.
If you've done any thing involving a keyboard recently, you'll have noticed how much you rely on RepeatKeys. For example, when you hold down the backspace to delete something you typed, thats a RepeatKey. CEGUI does not handle this automatically, as it is a GUI Library, not an input library. And since CEGUI only takes single inputs, we need to do some work on our own. I recently stumbled across an example on another website (See references) that was a good example of this. So we'll implement it ourselves to see how to use it with CEGUI. Below is the code.
The Header File
#pragma once #include <OIS.h> class AutoRepeatKey { private: OIS::KeyCode m_nKey; unsigned int m_nChar; float m_fElapsed; float m_fDelay; float m_fRepeatDelay; float m_fInitialDelay; protected: virtual void repeatKey(OIS::KeyCode a_nKey, unsigned int a_nChar) = 0; public: AutoRepeatKey(float a_fRepeatDelay = 0.035f, float a_fInitialDelay = 0.300f); void begin(const OIS::KeyEvent &evt); void end(const OIS::KeyEvent &evt); void update(float a_fElapsed); // Elapsed time in seconds };
The CPP File
#include "AutoRepeatKey.h" AutoRepeatKey::AutoRepeatKey(float a_fRepeatDelay, float a_fInitialDelay): m_nKey(OIS::KC_UNASSIGNED), m_fRepeatDelay(a_fRepeatDelay), m_fInitialDelay(a_fInitialDelay) {} void AutoRepeatKey::begin(const OIS::KeyEvent &evt) { m_nKey = evt.key; m_nChar = evt.text; m_fElapsed = 0; m_fDelay = m_fInitialDelay; } void AutoRepeatKey::end(const OIS::KeyEvent &evt) { if (m_nKey != evt.key) return; m_nKey = OIS::KC_UNASSIGNED; } void AutoRepeatKey::update(float a_fElapsed) { if (m_nKey == OIS::KC_UNASSIGNED) return; m_fElapsed += a_fElapsed; if (m_fElapsed < m_fDelay) return; m_fElapsed -= m_fDelay; m_fDelay = m_fRepeatDelay; do { repeatKey(m_nKey, m_nChar); m_fElapsed -= m_fRepeatDelay; } while (m_fElapsed >= m_fRepeatDelay); m_fElapsed = 0; }
Understanding the Code
if you think about how an auto key works there is a sequence of events that take place. The key is pressed down and held for a certain period of time. After that threshold of time with the key down is hit, it begins to press that key every so often. Then once the key is released, it will stop injecting key inputs.
Applying our knowledge
Pretty straight forward. So lets do some implementation.
Previously we wrote a few lines of code that injected input to CEGUI. Because there is a virtual function located in AutoRepeatKey we have to create a definition of that. We'll have to create a custom class, and then inherit AutoRepatKey:
class MyInput : public AutoRepeatKey public: void InjectOISKey(bool bButtonDown, OIS::KeyEvent inKey) { if (bButtonDown) { CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyDown(inKey.key); CEGUI::System::getSingleton().getDefaultGUIContext().injectChar(inKey.text); } else { CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyUp(inKey.key); } }; protected: void repeatKey(OIS::KeyCode a_nKey, unsigned int a_nChar){};
This is what we're gonna start with. Its pretty simple. Right now the program will compile, but do nothing different than the previous time we visited the Input Tutorial.
Now, we decided that we need to acknowledge the KeyDown as the start of the autokey, and the keyup as the ending. InjectOISKey is where we actually deal with the key being up or down, so lets mark our start and stop of the autokey detection there. Change the InjectOISKey function to be as follows
void InjectOISKey(bool bButtonDown, OIS::KeyEvent inKey) { if (bButtonDown) { CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyDown(inKey.key); CEGUI::System::getSingleton().getDefaultGUIContext().injectChar(inKey.text); begin(inKey); // <---- New line: Trigger beginning of AutoKeyRepeat } else { CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyUp(inKey.key); end(inKey); // <----- New Line: Trigger end of AutoKeyRepeat } };
Those will start and stop the internal timers for the autokeyrepeat.
Now we need to determine how exactly we will handle each of the key repeats. This will be pretty simple, as we just need to do the same stuff we normally do during a keypress. Inject the KeyDown to CEGUI, inject the Char, inject the KeyUp. Tho we don't want to just call the above function again or it would call begin/end and stuff could get strange and ugly.
Here is the definition of repeatKey we will use
void repeatKey(OIS::KeyCode a_nKey, unsigned int a_nChar) { // Now remember the key is still down, so we need to simulate the key being released, and then repressed immediatly CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyUp(a_nKey.key); // Key UP CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyDown(a_nKey.key); // Key Down CEGUI::System::getSingleton().getDefaultGUIContext().injectChar(a_nChar); // What that key means };
Now repeatKey will be called once the key has been depressed longer than our initial delay time, and will be called again for everytime the delay between keyrepeats has been met. But we need to tell this autokey about it. So somehwere in your code (probably around your Polling calls) you will want to call:
MyInput::update(float inTimeDelta);
You may want to add some functions to AutoRepeatKey to allow you to adjust m_fRepeatDelay and m_fInitialDelay. right now they are adjusted by the constructor of AutoRepeatKey which may not be optimal for your implementation.
Conclusion
Well there you have it, stolen and ripped code implemented into our input handler. But then again, you're using CEGUI so using foreign and alien code shouldn't be anything daunting!
Till next time!