diff options
Diffstat (limited to 'src/ui/encoder_input.cpp')
| -rw-r--r-- | src/ui/encoder_input.cpp | 191 |
1 files changed, 187 insertions, 4 deletions
diff --git a/src/ui/encoder_input.cpp b/src/ui/encoder_input.cpp index 62cb4c8b..f6f74aaf 100644 --- a/src/ui/encoder_input.cpp +++ b/src/ui/encoder_input.cpp @@ -10,11 +10,24 @@ #include <memory> #include "core/lv_group.h" +#include "esp_timer.h" #include "gpios.hpp" #include "hal/lv_hal_indev.h" +#include "nvs.hpp" #include "relative_wheel.hpp" #include "touchwheel.hpp" +constexpr int kDPadAngleThreshold = 20; +constexpr int kLongPressDelayMs = 500; +constexpr int kRepeatDelayMs = 250; + +static inline auto IsAngleWithin(int16_t wheel_angle, + int16_t target_angle, + int threshold) -> bool { + int16_t difference = (wheel_angle - target_angle + 127 + 255) % 255 - 127; + return difference <= threshold && difference >= -threshold; +} + namespace ui { static void encoder_read(lv_indev_drv_t* drv, lv_indev_data_t* data) { @@ -25,7 +38,9 @@ static void encoder_read(lv_indev_drv_t* drv, lv_indev_data_t* data) { EncoderInput::EncoderInput(drivers::IGpios& gpios, drivers::TouchWheel& wheel) : gpios_(gpios), raw_wheel_(wheel), - relative_wheel_(std::make_unique<drivers::RelativeWheel>(wheel)) { + relative_wheel_(std::make_unique<drivers::RelativeWheel>(wheel)), + scroller_(std::make_unique<Scroller>()), + mode_(drivers::NvsStorage::InputModes::kRotatingWheel) { lv_indev_drv_init(&driver_); driver_.type = LV_INDEV_TYPE_ENCODER; driver_.read_cb = encoder_read; @@ -37,10 +52,178 @@ EncoderInput::EncoderInput(drivers::IGpios& gpios, drivers::TouchWheel& wheel) auto EncoderInput::Read(lv_indev_data_t* data) -> void { raw_wheel_.Update(); relative_wheel_->Update(); + // GPIOs updating is handled by system_fsm. + + uint64_t now_ms = esp_timer_get_time() / 1000; + + // Deal with the potential overflow of our timer. + for (auto& it : touch_time_ms_) { + if (it.second > now_ms) { + // esp_timer overflowed. + it.second = 0; + } + } + + // 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)); + + 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); + + HandleKey( + Keys::kDirectionalUp, now_ms, + wheel_data.is_wheel_touched && + IsAngleWithin(wheel_data.wheel_position, 0, kDPadAngleThreshold)); + HandleKey( + Keys::kDirectionalLeft, now_ms, + wheel_data.is_wheel_touched && + IsAngleWithin(wheel_data.wheel_position, 63, kDPadAngleThreshold)); + HandleKey( + Keys::kDirectionalDown, now_ms, + wheel_data.is_wheel_touched && + IsAngleWithin(wheel_data.wheel_position, 127, kDPadAngleThreshold)); + HandleKey( + 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. + 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 + } + 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)) { + data->enc_diff = scroller_->AddInput(now_ms, -1); + } else if (ShortPressTriggerRepeating(Keys::kVolumeDown, now_ms)) { + data->enc_diff = scroller_->AddInput(now_ms, 1); + } + + 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)) { + data->enc_diff = scroller_->AddInput(now_ms, -1); + } else if (ShortPressTriggerRepeating(Keys::kDirectionalDown, now_ms)) { + data->enc_diff = scroller_->AddInput(now_ms, 1); + } else if (ShortPressTrigger(Keys::kDirectionalRight)) { + // TODO: ??? + } else if (ShortPressTrigger(Keys::kDirectionalLeft)) { + // TODO: Back button event. + } + + 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. + break; + case drivers::NvsStorage::InputModes::kRotatingWheel: + if (!raw_wheel_.GetTouchWheelData().is_wheel_touched) { + data->enc_diff = scroller_->AddInput(now_ms, 0); + } else if (relative_wheel_->ticks() != 0) { + data->enc_diff = scroller_->AddInput(now_ms, relative_wheel_->ticks()); + } else { + data->enc_diff = 0; + } + data->state = relative_wheel_->is_clicking() ? LV_INDEV_STATE_PRESSED + : LV_INDEV_STATE_RELEASED; + // TODO: Long-press events. + break; + } - data->enc_diff = relative_wheel_->ticks(); - data->state = relative_wheel_->is_clicking() ? LV_INDEV_STATE_PRESSED - : LV_INDEV_STATE_RELEASED; + // 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); + 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; + } + 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::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; + } + return false; +} + +auto Scroller::AddInput(uint64_t ms, int direction) -> int { + bool dir_changed = + ((velocity_ < 0 && direction > 0) || (velocity_ > 0 && direction < 0)); + if (direction == 0 || dir_changed) { + last_input_ms_ = ms; + velocity_ = 0; + return 0; + } + // Decay with time + if (last_input_ms_ > ms) { + last_input_ms_ = 0; + } + uint diff = ms - last_input_ms_; + uint diff_steps = diff / 25; + last_input_ms_ = ms + (last_input_ms_ % 50); + // Use powers of two for our exponential decay so we can implement decay + // trivially via bit shifting. + velocity_ >>= diff_steps; + + velocity_ += direction * 1000; + if (velocity_ > 0) { + return (velocity_ + 500) / 1000; + } else { + return (velocity_ - 500) / 1000; + } +} } // namespace ui |
