Keys: which ones are held, not only which were pressed

Apps heard a key once, when it went down. A Game that moves something
for as long as a key is held (issue #101) asks the key mapper instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
This commit is contained in:
2026-10-13 22:59:05 +02:00
co-authored by Claude Opus 5.5
parent 8aa2848c5f
commit 5c660d866d
3 changed files with 77 additions and 0 deletions
+18
View File
@@ -54,6 +54,24 @@ KeyEvent keyEvent(Key key, const RawKeys& keys) {
} // namespace } // 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<KeyEvent> KeyMapper::update(const RawKeys& keys) { std::vector<KeyEvent> KeyMapper::update(const RawKeys& keys) {
std::vector<KeyEvent> out; std::vector<KeyEvent> out;
+6
View File
@@ -33,6 +33,12 @@ class KeyMapper {
// don't need Fn. // don't need Fn.
void setTextEntry(bool active) { textEntry_ = active; } 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. // 0 when idle, '*' when opt was pressed, or the accent character waiting for its letter.
char pendingCompose() const { return compose_; } char pendingCompose() const { return compose_; }
+53
View File
@@ -301,6 +301,56 @@ void test_question_mark_is_help_only_when_not_typing() {
TEST_ASSERT_EQUAL('?', ev[0].ch); 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() { int main() {
UNITY_BEGIN(); UNITY_BEGIN();
RUN_TEST(test_plain_character); 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_fn_p_is_a_screenshot_in_both_modes);
RUN_TEST(test_plain_h_still_types); RUN_TEST(test_plain_h_still_types);
RUN_TEST(test_question_mark_is_help_only_when_not_typing); 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(); return UNITY_END();
} }