Public Access
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:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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_; }
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user