diff --git a/lib/input/src/key_mapper.cpp b/lib/input/src/key_mapper.cpp index 75ab490..fc59f5f 100644 --- a/lib/input/src/key_mapper.cpp +++ b/lib/input/src/key_mapper.cpp @@ -54,6 +54,24 @@ KeyEvent keyEvent(Key key, const RawKeys& keys) { } // namespace +bool KeyMapper::heldChar(char c) const { + return std::find(previous_.chars.begin(), previous_.chars.end(), c) != previous_.chars.end(); +} + +bool KeyMapper::held(Key key) const { + bool arrows = previous_.fn || !textEntry_; + switch (key) { + case Key::Up: return arrows && heldChar(';'); + case Key::Down: return arrows && heldChar('.'); + case Key::Left: return arrows && heldChar(','); + case Key::Right: return arrows && heldChar('/'); + case Key::Select: return previous_.enter; + case Key::Delete: return previous_.del; + case Key::Tab: return previous_.tab; + default: return false; + } +} + std::vector KeyMapper::update(const RawKeys& keys) { std::vector out; diff --git a/lib/input/src/key_mapper.h b/lib/input/src/key_mapper.h index 52abcc8..06fe4ef 100644 --- a/lib/input/src/key_mapper.h +++ b/lib/input/src/key_mapper.h @@ -33,6 +33,12 @@ class KeyMapper { // don't need Fn. void setTextEntry(bool active) { textEntry_ = active; } + // Is this key down right now, as of the last update()? Events only say when a key goes down: + // a Game that moves something for as long as a key is held asks this (issue #101). The arrows + // are ; . , / as for events: alone outside Text Entry, with Fn anywhere. + bool held(Key key) const; + bool heldChar(char c) const; // a character key, as it types + // 0 when idle, '*' when opt was pressed, or the accent character waiting for its letter. char pendingCompose() const { return compose_; } diff --git a/test/test_key_mapper/test_key_mapper.cpp b/test/test_key_mapper/test_key_mapper.cpp index cae163f..bdf8b09 100644 --- a/test/test_key_mapper/test_key_mapper.cpp +++ b/test/test_key_mapper/test_key_mapper.cpp @@ -301,6 +301,56 @@ void test_question_mark_is_help_only_when_not_typing() { TEST_ASSERT_EQUAL('?', ev[0].ch); } +void test_held_keys_are_known_between_events() { + KeyMapper m; + m.setTextEntry(false); + TEST_ASSERT_FALSE(m.held(Key::Up)); + RawKeys keys; + keys.chars = {';'}; + TEST_ASSERT_EQUAL(1, m.update(keys).size()); // the press, once + TEST_ASSERT_TRUE(m.held(Key::Up)); + TEST_ASSERT_FALSE(m.held(Key::Down)); + TEST_ASSERT_EQUAL(0, m.update(keys).size()); // still held: no new event, still held + TEST_ASSERT_TRUE(m.held(Key::Up)); + keys.chars = {';', '/'}; // a second key with it + m.update(keys); + TEST_ASSERT_TRUE(m.held(Key::Up)); + TEST_ASSERT_TRUE(m.held(Key::Right)); + keys.chars = {'/'}; + m.update(keys); + TEST_ASSERT_FALSE(m.held(Key::Up)); + TEST_ASSERT_TRUE(m.held(Key::Right)); + m.update(RawKeys()); + TEST_ASSERT_FALSE(m.held(Key::Right)); +} + +void test_held_arrows_need_fn_while_typing() { + KeyMapper m; + m.setTextEntry(true); + RawKeys keys; + keys.chars = {'.'}; + m.update(keys); + TEST_ASSERT_FALSE(m.held(Key::Down)); // it types a full stop + TEST_ASSERT_TRUE(m.heldChar('.')); + keys.fn = true; + m.update(keys); + TEST_ASSERT_TRUE(m.held(Key::Down)); +} + +void test_held_characters_and_enter() { + KeyMapper m; + m.setTextEntry(false); + RawKeys keys; + keys.chars = {'z', ' '}; + keys.enter = true; + m.update(keys); + TEST_ASSERT_TRUE(m.heldChar('z')); + TEST_ASSERT_TRUE(m.heldChar(' ')); + TEST_ASSERT_FALSE(m.heldChar('x')); + TEST_ASSERT_TRUE(m.held(Key::Select)); + TEST_ASSERT_FALSE(m.held(Key::Back)); // not asked for by anything: only what a Game steers with +} + int main() { UNITY_BEGIN(); RUN_TEST(test_plain_character); @@ -327,5 +377,8 @@ int main() { RUN_TEST(test_fn_p_is_a_screenshot_in_both_modes); RUN_TEST(test_plain_h_still_types); RUN_TEST(test_question_mark_is_help_only_when_not_typing); + RUN_TEST(test_held_keys_are_known_between_events); + RUN_TEST(test_held_arrows_need_fn_while_typing); + RUN_TEST(test_held_characters_and_enter); return UNITY_END(); }