From 5c04e2ad8d14616ce3ca09b658d25bab3d8d2460 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 4 Oct 2023 10:18:31 +1100 Subject: [PATCH] Neaten up the various kinds of inputs, and move volumes to The New Way --- src/audio/audio_fsm.cpp | 10 +- src/audio/include/audio_events.hpp | 2 + src/audio/include/audio_fsm.hpp | 4 +- src/system_fsm/include/system_events.hpp | 6 - src/system_fsm/system_fsm.cpp | 14 -- src/ui/encoder_input.cpp | 243 ++++++++++++++++------- src/ui/include/encoder_input.hpp | 29 ++- 7 files changed, 204 insertions(+), 104 deletions(-) diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index 6ea4f60d..e470300f 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -50,16 +50,14 @@ std::shared_ptr AudioState::sOutput; std::optional AudioState::sCurrentTrack; -void AudioState::react(const system_fsm::KeyUpChanged& ev) { - if (ev.falling && sOutput->AdjustVolumeUp()) { - ESP_LOGI(kTag, "volume up!"); +void AudioState::react(const StepUpVolume& ev) { + if (sOutput->AdjustVolumeUp()) { events::Ui().Dispatch(VolumeChanged{}); } } -void AudioState::react(const system_fsm::KeyDownChanged& ev) { - if (ev.falling && sOutput->AdjustVolumeDown()) { - ESP_LOGI(kTag, "volume down!"); +void AudioState::react(const StepDownVolume& ev) { + if (sOutput->AdjustVolumeDown()) { events::Ui().Dispatch(VolumeChanged{}); } } diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp index 6b986462..b130938c 100644 --- a/src/audio/include/audio_events.hpp +++ b/src/audio/include/audio_events.hpp @@ -34,6 +34,8 @@ struct PlayFile : tinyfsm::Event { std::pmr::string filename; }; +struct StepUpVolume : tinyfsm::Event {}; +struct StepDownVolume : tinyfsm::Event {}; struct VolumeChanged : tinyfsm::Event {}; struct ChangeMaxVolume : tinyfsm::Event { uint16_t new_max; diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp index 1c0b8aaa..256e1430 100644 --- a/src/audio/include/audio_fsm.hpp +++ b/src/audio/include/audio_fsm.hpp @@ -41,8 +41,8 @@ class AudioState : public tinyfsm::Fsm { /* Fallback event handler. Does nothing. */ void react(const tinyfsm::Event& ev) {} - void react(const system_fsm::KeyUpChanged&); - void react(const system_fsm::KeyDownChanged&); + void react(const StepUpVolume&); + void react(const StepDownVolume&); void react(const system_fsm::HasPhonesChanged&); void react(const ChangeMaxVolume&); void react(const OutputModeChanged&); diff --git a/src/system_fsm/include/system_events.hpp b/src/system_fsm/include/system_events.hpp index 7b21dbb5..4db9beb0 100644 --- a/src/system_fsm/include/system_events.hpp +++ b/src/system_fsm/include/system_events.hpp @@ -41,12 +41,6 @@ struct StorageMounted : tinyfsm::Event {}; struct StorageError : tinyfsm::Event {}; -struct KeyUpChanged : tinyfsm::Event { - bool falling; -}; -struct KeyDownChanged : tinyfsm::Event { - bool falling; -}; struct KeyLockChanged : tinyfsm::Event { bool falling; }; diff --git a/src/system_fsm/system_fsm.cpp b/src/system_fsm/system_fsm.cpp index e048cec7..9e1a4198 100644 --- a/src/system_fsm/system_fsm.cpp +++ b/src/system_fsm/system_fsm.cpp @@ -31,28 +31,14 @@ void SystemState::react(const FatalError& err) { void SystemState::react(const internal::GpioInterrupt&) { auto& gpios = sServices->gpios(); - bool prev_key_up = gpios.Get(drivers::Gpios::Pin::kKeyUp); - bool prev_key_down = gpios.Get(drivers::Gpios::Pin::kKeyDown); bool prev_key_lock = gpios.Get(drivers::Gpios::Pin::kKeyLock); bool prev_has_headphones = !gpios.Get(drivers::Gpios::Pin::kPhoneDetect); gpios.Read(); - bool key_up = gpios.Get(drivers::Gpios::Pin::kKeyUp); - bool key_down = gpios.Get(drivers::Gpios::Pin::kKeyDown); bool key_lock = gpios.Get(drivers::Gpios::Pin::kKeyLock); bool has_headphones = !gpios.Get(drivers::Gpios::Pin::kPhoneDetect); - if (key_up != prev_key_up) { - KeyUpChanged ev{.falling = prev_key_up}; - events::Audio().Dispatch(ev); - events::Ui().Dispatch(ev); - } - if (key_down != prev_key_down) { - KeyDownChanged ev{.falling = prev_key_down}; - events::Audio().Dispatch(ev); - events::Ui().Dispatch(ev); - } if (key_lock != prev_key_lock) { KeyLockChanged ev{.falling = key_lock}; events::System().Dispatch(ev); diff --git a/src/ui/encoder_input.cpp b/src/ui/encoder_input.cpp index 9aa5d29a..0345665b 100644 --- a/src/ui/encoder_input.cpp +++ b/src/ui/encoder_input.cpp @@ -9,13 +9,16 @@ #include #include +#include "audio_events.hpp" #include "core/lv_group.h" #include "esp_timer.h" +#include "event_queue.hpp" #include "gpios.hpp" #include "hal/lv_hal_indev.h" #include "nvs.hpp" #include "relative_wheel.hpp" #include "touchwheel.hpp" +#include "ui_events.hpp" constexpr int kDPadAngleThreshold = 20; constexpr int kLongPressDelayMs = 500; @@ -70,83 +73,139 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { } // Check each button. - HandleKey(Keys::kVolumeUp, now_ms, !gpios_.Get(drivers::IGpios::Pin::kKeyUp)); - HandleKey(Keys::kVolumeDown, now_ms, - !gpios_.Get(drivers::IGpios::Pin::kKeyDown)); + HandleKeyState(Keys::kVolumeUp, now_ms, + !gpios_.Get(drivers::IGpios::Pin::kKeyUp)); + HandleKeyState(Keys::kVolumeDown, now_ms, + !gpios_.Get(drivers::IGpios::Pin::kKeyDown)); drivers::TouchWheelData wheel_data = raw_wheel_.GetTouchWheelData(); - HandleKey(Keys::kTouchWheel, now_ms, wheel_data.is_wheel_touched); - HandleKey(Keys::kTouchWheelCenter, now_ms, wheel_data.is_button_touched); + HandleKeyState(Keys::kTouchWheel, now_ms, wheel_data.is_wheel_touched); + HandleKeyState(Keys::kTouchWheelCenter, now_ms, wheel_data.is_button_touched); - HandleKey( + HandleKeyState( Keys::kDirectionalUp, now_ms, wheel_data.is_wheel_touched && IsAngleWithin(wheel_data.wheel_position, 0, kDPadAngleThreshold)); - HandleKey( + HandleKeyState( Keys::kDirectionalLeft, now_ms, wheel_data.is_wheel_touched && IsAngleWithin(wheel_data.wheel_position, 63, kDPadAngleThreshold)); - HandleKey( + HandleKeyState( Keys::kDirectionalDown, now_ms, wheel_data.is_wheel_touched && IsAngleWithin(wheel_data.wheel_position, 127, kDPadAngleThreshold)); - HandleKey( + HandleKeyState( Keys::kDirectionalRight, now_ms, wheel_data.is_wheel_touched && IsAngleWithin(wheel_data.wheel_position, 189, kDPadAngleThreshold)); // We now have enough information to give LVGL its update. + Trigger trigger; switch (mode_) { case drivers::NvsStorage::InputModes::kButtonsOnly: data->state = LV_INDEV_STATE_RELEASED; - if (ShortPressTrigger(Keys::kVolumeUp)) { - data->enc_diff = -1; - } else if (ShortPressTrigger(Keys::kVolumeDown)) { - data->enc_diff = 1; - } else if (LongPressTrigger(Keys::kVolumeDown, now_ms)) { - data->state = LV_INDEV_STATE_PRESSED; - } else if (LongPressTrigger(Keys::kVolumeUp, now_ms)) { - // TODO: Back button event + + trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kLongPress, now_ms); + switch (trigger) { + case Trigger::kNone: + break; + case Trigger::kClick: + data->enc_diff = -1; + break; + case Trigger::kLongPress: + events::Ui().Dispatch(internal::BackPressed{}); + break; + } + + trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kLongPress, now_ms); + switch (trigger) { + case Trigger::kNone: + break; + case Trigger::kClick: + data->enc_diff = 1; + break; + case Trigger::kLongPress: + data->state = LV_INDEV_STATE_PRESSED; + break; } + break; case drivers::NvsStorage::InputModes::kButtonsWithWheel: - data->state = ShortPressTrigger(Keys::kTouchWheel) - ? LV_INDEV_STATE_PRESSED - : LV_INDEV_STATE_RELEASED; - if (ShortPressTriggerRepeating(Keys::kVolumeUp, now_ms)) { + trigger = TriggerKey(Keys::kTouchWheel, KeyStyle::kLongPress, now_ms); + data->state = trigger == Trigger::kClick ? LV_INDEV_STATE_PRESSED + : LV_INDEV_STATE_RELEASED; + + trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kRepeat, now_ms); + if (trigger == Trigger::kClick) { data->enc_diff = scroller_->AddInput(now_ms, -1); - } else if (ShortPressTriggerRepeating(Keys::kVolumeDown, now_ms)) { + } + + trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kRepeat, now_ms); + if (trigger == Trigger::kClick) { data->enc_diff = scroller_->AddInput(now_ms, 1); } + // Cancel scrolling if the buttons are released. if (!touch_time_ms_.contains(Keys::kVolumeDown) && !touch_time_ms_.contains(Keys::kVolumeUp)) { data->enc_diff = scroller_->AddInput(now_ms, 0); } - // TODO: Long-press events. + break; case drivers::NvsStorage::InputModes::kDirectionalWheel: - data->state = ShortPressTrigger(Keys::kTouchWheelCenter) - ? LV_INDEV_STATE_PRESSED - : LV_INDEV_STATE_RELEASED; - if (!ShortPressTriggerRepeating(Keys::kTouchWheel, now_ms)) { - break; - } - if (ShortPressTriggerRepeating(Keys::kDirectionalUp, now_ms)) { + trigger = + TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, now_ms); + data->state = trigger == Trigger::kClick ? LV_INDEV_STATE_PRESSED + : LV_INDEV_STATE_RELEASED; + + trigger = TriggerKey(Keys::kDirectionalUp, KeyStyle::kRepeat, now_ms); + if (trigger == Trigger::kClick) { data->enc_diff = scroller_->AddInput(now_ms, -1); - } else if (ShortPressTriggerRepeating(Keys::kDirectionalDown, now_ms)) { + } + + trigger = TriggerKey(Keys::kDirectionalDown, KeyStyle::kRepeat, now_ms); + if (trigger == Trigger::kClick) { data->enc_diff = scroller_->AddInput(now_ms, 1); - } else if (ShortPressTrigger(Keys::kDirectionalRight)) { + } + + trigger = TriggerKey(Keys::kDirectionalLeft, KeyStyle::kRepeat, now_ms); + if (trigger == Trigger::kClick) { + events::Ui().Dispatch(internal::BackPressed{}); + } + + trigger = TriggerKey(Keys::kDirectionalRight, KeyStyle::kRepeat, now_ms); + if (trigger == Trigger::kClick) { // TODO: ??? - } else if (ShortPressTrigger(Keys::kDirectionalLeft)) { - // TODO: Back button event. } + // Cancel scrolling if the touchpad is released. if (!touch_time_ms_.contains(Keys::kDirectionalUp) && !touch_time_ms_.contains(Keys::kDirectionalDown)) { data->enc_diff = scroller_->AddInput(now_ms, 0); } - // TODO: Long-press events. + + trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kLongPress, now_ms); + switch (trigger) { + case Trigger::kNone: + break; + case Trigger::kClick: + events::Audio().Dispatch(audio::StepUpVolume{}); + break; + case Trigger::kLongPress: + break; + } + + trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kLongPress, now_ms); + switch (trigger) { + case Trigger::kNone: + break; + case Trigger::kClick: + events::Audio().Dispatch(audio::StepDownVolume{}); + break; + case Trigger::kLongPress: + break; + } + break; case drivers::NvsStorage::InputModes::kRotatingWheel: if (!raw_wheel_.GetTouchWheelData().is_wheel_touched) { @@ -156,53 +215,97 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { } else { data->enc_diff = 0; } - data->state = relative_wheel_->is_clicking() ? LV_INDEV_STATE_PRESSED - : LV_INDEV_STATE_RELEASED; - // TODO: Long-press events. + + trigger = + TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, now_ms); + switch (trigger) { + case Trigger::kNone: + data->state = LV_INDEV_STATE_RELEASED; + break; + case Trigger::kClick: + data->state = LV_INDEV_STATE_PRESSED; + break; + case Trigger::kLongPress: + // TODO: ??? + data->state = LV_INDEV_STATE_PRESSED; + break; + } + + trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kLongPress, now_ms); + switch (trigger) { + case Trigger::kNone: + break; + case Trigger::kClick: + events::Audio().Dispatch(audio::StepUpVolume{}); + break; + case Trigger::kLongPress: + break; + } + + trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kLongPress, now_ms); + switch (trigger) { + case Trigger::kNone: + break; + case Trigger::kClick: + events::Audio().Dispatch(audio::StepDownVolume{}); + break; + case Trigger::kLongPress: + break; + } + break; } - - // TODO: Apply inertia / acceleration. } -auto EncoderInput::HandleKey(Keys key, uint64_t ms, bool clicked) -> void { - if (!clicked) { - touch_time_ms_.erase(key); - short_press_fired_.erase(key); - long_press_fired_.erase(key); +auto EncoderInput::HandleKeyState(Keys key, uint64_t ms, bool clicked) -> void { + if (clicked) { + if (!touch_time_ms_.contains(key)) { + // Key was just pressed + touch_time_ms_[key] = ms; + just_released_.erase(key); + fired_.erase(key); + } return; } - if (!touch_time_ms_.contains(key)) { - touch_time_ms_[key] = ms; - } -} -auto EncoderInput::ShortPressTrigger(Keys key) -> bool { - if (touch_time_ms_.contains(key) && !short_press_fired_.contains(key)) { - short_press_fired_[key] = true; - return true; + // Key is not clicked. + if (touch_time_ms_.contains(key)) { + // Key was just released. + just_released_.insert(key); + touch_time_ms_.erase(key); } - return false; } -auto EncoderInput::ShortPressTriggerRepeating(Keys key, uint64_t ms) -> bool { - if (touch_time_ms_.contains(key) && - (!short_press_fired_.contains(key) || - ms - touch_time_ms_[key] >= kRepeatDelayMs)) { - touch_time_ms_[key] = ms; - short_press_fired_[key] = true; - return true; - } - return false; -} +auto EncoderInput::TriggerKey(Keys key, KeyStyle s, uint64_t ms) -> Trigger { + if (s == KeyStyle::kRepeat) { + bool may_repeat = fired_.contains(key) && touch_time_ms_.contains(key) && + ms - touch_time_ms_[key] >= kRepeatDelayMs; -auto EncoderInput::LongPressTrigger(Keys key, uint64_t ms) -> bool { - if (touch_time_ms_.contains(key) && !long_press_fired_.contains(key) && - ms - touch_time_ms_[key] >= kLongPressDelayMs) { - long_press_fired_[key] = true; - return true; + // Repeatable keys trigger on press. + if (touch_time_ms_.contains(key) && (!fired_.contains(key) || may_repeat)) { + fired_.insert(key); + return Trigger::kClick; + } else { + return Trigger::kNone; + } + } else if (s == KeyStyle::kLongPress) { + // Long press keys trigger on release, or after holding for a delay. + if (just_released_.contains(key)) { + just_released_.erase(key); + if (!fired_.contains(key)) { + fired_.insert(key); + return Trigger::kClick; + } + } + if (touch_time_ms_.contains(key) && + ms - touch_time_ms_[key] >= kLongPressDelayMs && + !fired_.contains(key)) { + fired_.insert(key); + return Trigger::kLongPress; + } } - return false; + + return Trigger::kNone; } auto Scroller::AddInput(uint64_t ms, int direction) -> int { diff --git a/src/ui/include/encoder_input.hpp b/src/ui/include/encoder_input.hpp index e685a7a2..2386b5c9 100644 --- a/src/ui/include/encoder_input.hpp +++ b/src/ui/include/encoder_input.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "core/lv_group.h" #include "gpios.hpp" @@ -51,6 +52,7 @@ class EncoderInput { drivers::NvsStorage::InputModes mode_; bool is_locked_; + // Every kind of distinct input that we could map to an action. enum class Keys { kVolumeUp, kVolumeDown, @@ -62,14 +64,29 @@ class EncoderInput { kDirectionalLeft, }; + // Map from a Key, to the time that it was first touched in ms. If the key is + // currently released, where will be no entry. std::unordered_map touch_time_ms_; - std::unordered_map short_press_fired_; - std::unordered_map long_press_fired_; + // Set of keys that were released during the current update. + std::set just_released_; + // Set of keys that have had an event fired for them since being pressed. + std::set fired_; + + enum class Trigger { + kNone, + // Regular short-click. Triggered on release for long-pressable keys, + // triggered on the initial press for repeatable keys. + kClick, + kLongPress, + }; + + enum class KeyStyle { + kRepeat, + kLongPress, + }; - auto HandleKey(Keys key, uint64_t ms, bool clicked) -> void; - auto ShortPressTrigger(Keys key) -> bool; - auto ShortPressTriggerRepeating(Keys key, uint64_t ms) -> bool; - auto LongPressTrigger(Keys key, uint64_t ms) -> bool; + auto HandleKeyState(Keys key, uint64_t ms, bool clicked) -> void; + auto TriggerKey(Keys key, KeyStyle t, uint64_t ms) -> Trigger; }; class Scroller {