-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the KeyboardState class. This class is designed to be injected by a mock so that input code can be handled in tests. This is the first part of the input system, which will also include mouse and gamepad handlers as well as an abstract "actions" handler.
- Loading branch information
1 parent
c0a871a
commit 507ac14
Showing
5 changed files
with
255 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#ifndef GL_ADAGIO_KEYBOARDHANDLER_H | ||
#define GL_ADAGIO_KEYBOARDHANDLER_H | ||
|
||
#include <cstdint> | ||
|
||
namespace Adagio { | ||
|
||
typedef std::uint16_t keycode; | ||
|
||
struct KeyboardHandler { | ||
virtual keycode getNextKey() = 0; | ||
|
||
virtual char getNextChar() = 0; | ||
|
||
virtual bool isKeyDown(keycode) = 0; | ||
|
||
virtual bool isKeyUp(keycode) = 0; | ||
}; | ||
|
||
} | ||
|
||
#endif //GL_ADAGIO_KEYBOARDHANDLER_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
#include "KeyboardState.h" | ||
|
||
void Adagio::KeyboardState::setHandler(Adagio::KeyboardHandler *handle) { | ||
handler = handle; | ||
} | ||
|
||
bool Adagio::KeyboardState::isKeyDown(Adagio::keycode key) const { | ||
auto it = keys.find(key); | ||
if (it != keys.end()) { | ||
return it->second.keyDown; | ||
} | ||
return false; | ||
} | ||
|
||
bool Adagio::KeyboardState::isKeyUp(Adagio::keycode key) const { | ||
return !isKeyDown(key); | ||
} | ||
|
||
bool Adagio::KeyboardState::hasKeyPressStarted(Adagio::keycode key) const { | ||
auto it = keys.find(key); | ||
if (it != keys.end()) { | ||
return it->second.keyPressed; | ||
} | ||
return false; | ||
} | ||
|
||
bool Adagio::KeyboardState::hasKeyPressEnded(Adagio::keycode key) const { | ||
auto it = keys.find(key); | ||
if (it != keys.end()) { | ||
return it->second.keyReleased; | ||
} | ||
return false; | ||
} | ||
|
||
void Adagio::KeyboardState::update() { | ||
updateTextBuffer(); | ||
checkKnownKeys(); | ||
scanForNewKeyPresses(); | ||
} | ||
|
||
void Adagio::KeyboardState::updateTextBuffer() { | ||
for (auto &c: textBuffer) { | ||
if (c == 0) { | ||
break; | ||
} | ||
c = 0; | ||
} | ||
char bufferPos = 0; | ||
char c = handler->getNextChar(); | ||
while (c != 0) { | ||
textBuffer[bufferPos++] = c; | ||
if (bufferPos > 8) { | ||
break; | ||
} | ||
c = handler->getNextChar(); | ||
} | ||
} | ||
|
||
void Adagio::KeyboardState::checkKnownKeys() { | ||
for (auto &keyState: keys) { | ||
keyState.second.keyPressed = false; | ||
keyState.second.keyReleased = keyState.second.keyDown && handler->isKeyUp(keyState.first); | ||
keyState.second.keyDown = handler->isKeyDown(keyState.first); | ||
} | ||
} | ||
|
||
void Adagio::KeyboardState::scanForNewKeyPresses() { | ||
keycode newKey = handler->getNextKey(); | ||
while (newKey != 0) { | ||
keys[newKey].keyDown = true; | ||
keys[newKey].keyPressed = true; | ||
keys[newKey].keyReleased = false; | ||
newKey = handler->getNextKey(); | ||
} | ||
} | ||
|
||
Adagio::KeyboardState::KeyboardState() { | ||
keys.reserve(256); | ||
} | ||
|
||
const char *Adagio::KeyboardState::readTextBuffer() const { | ||
return textBuffer; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
#ifndef GL_ADAGIO_KEYBOARDSTATE_H | ||
#define GL_ADAGIO_KEYBOARDSTATE_H | ||
|
||
#include <unordered_map> | ||
#include <cstdint> | ||
#include "KeyboardHandler.h" | ||
|
||
namespace Adagio { | ||
class KeyboardState { | ||
public: | ||
KeyboardState(); | ||
|
||
void setHandler(KeyboardHandler *h); | ||
|
||
bool isKeyDown(keycode) const; | ||
|
||
bool isKeyUp(keycode) const; | ||
|
||
bool hasKeyPressStarted(keycode) const; | ||
|
||
bool hasKeyPressEnded(keycode) const; | ||
|
||
const char *readTextBuffer() const; | ||
|
||
void update(); | ||
|
||
private: | ||
struct KeyBitState { | ||
bool keyDown: 1; | ||
bool keyPressed: 1; | ||
bool keyReleased: 1; | ||
}; | ||
|
||
char textBuffer[8]{0, 0, 0, 0, 0, 0, 0, 0}; | ||
KeyboardHandler *handler{nullptr}; | ||
std::unordered_map<keycode, KeyBitState> keys; | ||
|
||
void scanForNewKeyPresses(); | ||
|
||
void checkKnownKeys(); | ||
|
||
void updateTextBuffer(); | ||
}; | ||
} | ||
|
||
|
||
#endif //GL_ADAGIO_KEYBOARDSTATE_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
#include <catch2/catch.hpp> | ||
#include "../../src/input/keyboard/KeyboardState.h" | ||
|
||
class MockKeyboard : public Adagio::KeyboardHandler { | ||
public: | ||
std::unordered_map<Adagio::keycode, bool> keys; | ||
std::vector<Adagio::keycode> pressedKeys; | ||
std::string buffer; | ||
|
||
void writeChars(const std::string &input) { | ||
buffer += input; | ||
} | ||
|
||
void pressKey(Adagio::keycode key) { | ||
keys[key] = true; | ||
pressedKeys.push_back(key); | ||
} | ||
|
||
void releaseKey(Adagio::keycode key) { | ||
keys[key] = false; | ||
} | ||
|
||
Adagio::keycode getNextKey() override { | ||
if (!pressedKeys.empty()) { | ||
Adagio::keycode key = pressedKeys.back(); | ||
pressedKeys.pop_back(); | ||
return key; | ||
} | ||
return 0; | ||
} | ||
|
||
char getNextChar() override { | ||
if (buffer.empty()) { | ||
return 0; | ||
} | ||
char c = buffer[0]; | ||
buffer.erase(buffer.begin()); | ||
return c; | ||
} | ||
|
||
bool isKeyDown(Adagio::keycode key) override { | ||
if (keys.find(key) != keys.end()) { | ||
return keys[key]; | ||
} | ||
return false; | ||
} | ||
|
||
bool isKeyUp(Adagio::keycode key) override { | ||
if (keys.find(key) != keys.end()) { | ||
return !keys[key]; | ||
} | ||
return true; | ||
} | ||
}; | ||
|
||
TEST_CASE("KeyboardState", "[input][keyboard]") { | ||
const Adagio::keycode TEST_KEY_1 = 128; | ||
MockKeyboard mockKeyboard; | ||
Adagio::KeyboardState keyboard; | ||
keyboard.setHandler(&mockKeyboard); | ||
|
||
SECTION("It can query an empty keyboard state") { | ||
REQUIRE_FALSE(keyboard.isKeyDown(TEST_KEY_1)); | ||
REQUIRE(keyboard.isKeyUp(TEST_KEY_1)); | ||
REQUIRE_FALSE(keyboard.hasKeyPressStarted(TEST_KEY_1)); | ||
REQUIRE_FALSE(keyboard.hasKeyPressEnded(TEST_KEY_1)); | ||
} | ||
|
||
SECTION("It can detect a key press") { | ||
mockKeyboard.pressKey(TEST_KEY_1); | ||
keyboard.update(); | ||
REQUIRE(keyboard.isKeyDown(TEST_KEY_1)); | ||
REQUIRE(keyboard.hasKeyPressStarted(TEST_KEY_1)); | ||
keyboard.update(); | ||
REQUIRE(keyboard.isKeyDown(TEST_KEY_1)); | ||
REQUIRE_FALSE(keyboard.hasKeyPressStarted(TEST_KEY_1)); | ||
} | ||
|
||
SECTION("It can detect a key release") { | ||
mockKeyboard.pressKey(TEST_KEY_1); | ||
keyboard.update(); | ||
REQUIRE_FALSE(keyboard.isKeyUp(TEST_KEY_1)); | ||
mockKeyboard.releaseKey(TEST_KEY_1); | ||
keyboard.update(); | ||
REQUIRE(keyboard.isKeyUp(TEST_KEY_1)); | ||
REQUIRE(keyboard.hasKeyPressEnded(TEST_KEY_1)); | ||
keyboard.update(); | ||
REQUIRE(keyboard.isKeyUp(TEST_KEY_1)); | ||
REQUIRE_FALSE(keyboard.hasKeyPressEnded(TEST_KEY_1)); | ||
} | ||
|
||
SECTION("It can read from a text input buffer") { | ||
mockKeyboard.writeChars("potato"); | ||
keyboard.update(); | ||
std::string input = keyboard.readTextBuffer(); | ||
REQUIRE(input == "potato"); | ||
keyboard.update(); | ||
input = keyboard.readTextBuffer(); | ||
REQUIRE(input.empty()); | ||
} | ||
} |