Public Access
Arrow keys work without Fn outside Text Entry
; . , / are arrows on their own unless the foreground App is editing text (App::textEntryActive); Fn + those keys are arrows everywhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -66,6 +66,10 @@ The regulatory band plan the device transmits under (here EU868). It sets the al
|
||||
**Duty Cycle Budget**:
|
||||
The share of airtime the Region allows this device to transmit. When it's used up, outgoing traffic waits.
|
||||
|
||||
**Text Entry**:
|
||||
When an App is editing text. During Text Entry, `;` `.` `,` `/` type their characters and Fn makes them arrows. Otherwise they are arrows on their own.
|
||||
_Avoid_: edit mode, insert mode
|
||||
|
||||
**Compose Key**:
|
||||
The `opt` key used as a dead key. Pressing it and then a base letter types an accented character (e.g. `opt` `'` `e` → é).
|
||||
_Avoid_: modifier, alt
|
||||
|
||||
@@ -22,6 +22,9 @@ class App {
|
||||
return false;
|
||||
}
|
||||
|
||||
// True while the App is editing text: the arrow keys then type ; . , / and need Fn to move.
|
||||
virtual bool textEntryActive() const { return false; }
|
||||
|
||||
// Called every main-loop pass while in the foreground (e.g. to refresh live values).
|
||||
virtual void update(uint32_t nowMs) { (void)nowMs; }
|
||||
|
||||
|
||||
@@ -87,14 +87,21 @@ void KeyMapper::onChar(char c, const RawKeys& keys, std::vector<KeyEvent>& out)
|
||||
return;
|
||||
}
|
||||
|
||||
if (keys.fn) {
|
||||
if (keys.fn || !textEntry_) {
|
||||
switch (c) {
|
||||
case ';': out.push_back(KeyEvent::of(Key::Up)); return;
|
||||
case '.': out.push_back(KeyEvent::of(Key::Down)); return;
|
||||
case ',': out.push_back(KeyEvent::of(Key::Left)); return;
|
||||
case '/': out.push_back(KeyEvent::of(Key::Right)); return;
|
||||
case '`': out.push_back(KeyEvent::of(Key::Home)); return;
|
||||
default: return; // other Fn combos are unassigned
|
||||
case '`':
|
||||
if (keys.fn) {
|
||||
out.push_back(KeyEvent::of(Key::Home));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (keys.fn) return; // other Fn combos are unassigned
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (c == '`') {
|
||||
|
||||
@@ -22,12 +22,17 @@ struct RawKeys {
|
||||
};
|
||||
|
||||
// Turns keyboard state changes into logical KeyEvents: only newly pressed keys produce events;
|
||||
// Fn + ; . , / are arrows; ` is Back and Fn + ` is Home; the Compose Key (opt) followed by an
|
||||
// accent and a letter types the accented letter (opt ' e -> é).
|
||||
// Fn + ; . , / are arrows, and so are ; . , / alone when no text is being entered; ` is Back and
|
||||
// Fn + ` is Home; the Compose Key (opt) followed by an accent and a letter types the accented
|
||||
// letter (opt ' e -> é).
|
||||
class KeyMapper {
|
||||
public:
|
||||
std::vector<KeyEvent> update(const RawKeys& keys);
|
||||
|
||||
// Whether the foreground App is editing text (default). Outside text entry, the arrow keys
|
||||
// don't need Fn.
|
||||
void setTextEntry(bool active) { textEntry_ = active; }
|
||||
|
||||
// 0 when idle, '*' when opt was pressed, or the accent character waiting for its letter.
|
||||
char pendingCompose() const { return compose_; }
|
||||
|
||||
@@ -36,6 +41,7 @@ class KeyMapper {
|
||||
|
||||
RawKeys previous_;
|
||||
char compose_ = 0;
|
||||
bool textEntry_ = true;
|
||||
};
|
||||
|
||||
} // namespace roro
|
||||
|
||||
@@ -11,7 +11,7 @@ const char* const kText =
|
||||
"Bienvenue sur roro9stack ! Ce texte teste le retour \xC3\xA0 la ligne automatique et les "
|
||||
"accents : \xC3\xA9t\xC3\xA9, \xC3\xA0 bient\xC3\xB4t, gar\xC3\xA7on, No\xC3\xABl, "
|
||||
"ma\xC3\xAEtre, o\xC3\xB9.\n\n"
|
||||
"Use Fn+; and Fn+. to scroll, ` to go back. Fn+` always returns to the Launcher.\n\n"
|
||||
"Use ; and . to scroll, ` to go back. Fn+` always returns to the Launcher.\n\n"
|
||||
"Unbreakablewordthatislongerthanawholelineofthescreen to test hard breaks.";
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ class DemoApp : public App {
|
||||
explicit DemoApp(EventBus& bus) : bus_(bus) {}
|
||||
void onEnter() override;
|
||||
bool onKey(const KeyEvent& e) override;
|
||||
bool textEntryActive() const override { return page_ == Page::Editor && !dialog_; }
|
||||
void draw(Canvas& c) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -34,6 +34,7 @@ class SettingsApp : public App {
|
||||
void onEnter() override;
|
||||
bool onKey(const KeyEvent& e) override;
|
||||
void update(uint32_t nowMs) override;
|
||||
bool textEntryActive() const override { return page_ == Page::Text; }
|
||||
void draw(Canvas& c) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -13,6 +13,9 @@ class SetupApp : public App {
|
||||
public:
|
||||
SetupApp(Settings& settings, AppManager& apps);
|
||||
bool onKey(const KeyEvent& e) override;
|
||||
bool textEntryActive() const override {
|
||||
return wizard_.step() == SetupWizard::Step::LongName || wizard_.step() == SetupWizard::Step::ShortName;
|
||||
}
|
||||
void draw(Canvas& c) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -143,6 +143,7 @@ void loop() {
|
||||
M5Cardputer.update();
|
||||
if (M5Cardputer.Keyboard.isChange()) {
|
||||
bool swallow = M5Cardputer.Keyboard.isPressed() && power->onKey(now); // only woke the screen
|
||||
keyMapper.setTextEntry(apps->foreground().textEntryActive());
|
||||
auto events = keyMapper.update(readKeys()); // always, so held keys are tracked
|
||||
if (!swallow)
|
||||
for (auto& e : events) apps->handleKey(e);
|
||||
|
||||
@@ -175,6 +175,51 @@ void test_compose_state_is_visible_for_the_ui() {
|
||||
TEST_ASSERT_EQUAL('^', m.pendingCompose());
|
||||
}
|
||||
|
||||
void test_arrow_keys_work_without_fn_when_not_typing() {
|
||||
KeyMapper m;
|
||||
m.setTextEntry(false);
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Key::Up), static_cast<int>(press(m, chars({';'}))[0].key));
|
||||
release(m);
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Key::Down), static_cast<int>(press(m, chars({'.'}))[0].key));
|
||||
release(m);
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Key::Left), static_cast<int>(press(m, chars({','}))[0].key));
|
||||
release(m);
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Key::Right), static_cast<int>(press(m, chars({'/'}))[0].key));
|
||||
}
|
||||
|
||||
void test_arrow_keys_type_characters_during_text_entry() {
|
||||
KeyMapper m;
|
||||
m.setTextEntry(true);
|
||||
auto ev = press(m, chars({';'}));
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Key::Char), static_cast<int>(ev[0].key));
|
||||
TEST_ASSERT_EQUAL(';', ev[0].ch);
|
||||
}
|
||||
|
||||
void test_fn_arrows_work_in_both_modes() {
|
||||
KeyMapper m;
|
||||
m.setTextEntry(false);
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Key::Up), static_cast<int>(press(m, withFn({';'}))[0].key));
|
||||
release(m);
|
||||
m.setTextEntry(true);
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Key::Up), static_cast<int>(press(m, withFn({';'}))[0].key));
|
||||
}
|
||||
|
||||
void test_other_characters_still_type_when_not_typing() {
|
||||
KeyMapper m;
|
||||
m.setTextEntry(false);
|
||||
auto ev = press(m, chars({'w'}));
|
||||
TEST_ASSERT_EQUAL('w', ev[0].ch);
|
||||
}
|
||||
|
||||
void test_shifted_arrow_keys_type_their_symbols_when_not_typing() {
|
||||
// Shift+; is ':' — the driver reports the shifted character, which isn't an arrow.
|
||||
KeyMapper m;
|
||||
m.setTextEntry(false);
|
||||
RawKeys r = chars({':'});
|
||||
r.shift = true;
|
||||
TEST_ASSERT_EQUAL(':', press(m, r)[0].ch);
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_plain_character);
|
||||
@@ -191,5 +236,10 @@ int main() {
|
||||
RUN_TEST(test_opt_followed_by_a_non_accent_types_it_normally);
|
||||
RUN_TEST(test_pressing_opt_twice_cancels);
|
||||
RUN_TEST(test_compose_state_is_visible_for_the_ui);
|
||||
RUN_TEST(test_arrow_keys_work_without_fn_when_not_typing);
|
||||
RUN_TEST(test_arrow_keys_type_characters_during_text_entry);
|
||||
RUN_TEST(test_fn_arrows_work_in_both_modes);
|
||||
RUN_TEST(test_other_characters_still_type_when_not_typing);
|
||||
RUN_TEST(test_shifted_arrow_keys_type_their_symbols_when_not_typing);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user