summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-10-03 14:33:36 +1100
committerjacqueline <me@jacqueline.id.au>2023-10-03 14:33:36 +1100
commit09f129662e41108fd86255c02623a38e07cbbb27 (patch)
tree2bfbfbcd0f3c509e1eae1a11016e6f7c0cb37206 /src/ui
parent7d5536e2abca61f503ed68521603bd30700a7e5e (diff)
downloadtangara-fw-09f129662e41108fd86255c02623a38e07cbbb27.tar.gz
Add scroll velocity + more input methods
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/encoder_input.cpp191
-rw-r--r--src/ui/include/encoder_input.hpp39
2 files changed, 226 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
diff --git a/src/ui/include/encoder_input.hpp b/src/ui/include/encoder_input.hpp
index 9c114e80..e685a7a2 100644
--- a/src/ui/include/encoder_input.hpp
+++ b/src/ui/include/encoder_input.hpp
@@ -6,17 +6,22 @@
#pragma once
+#include <stdint.h>
+#include <deque>
#include <memory>
#include "core/lv_group.h"
#include "gpios.hpp"
#include "hal/lv_hal_indev.h"
+#include "nvs.hpp"
#include "relative_wheel.hpp"
#include "touchwheel.hpp"
namespace ui {
+class Scroller;
+
/*
* Main input device abstracting that handles turning lower-level input device
* drivers into events and LVGL inputs.
@@ -31,6 +36,7 @@ class EncoderInput {
auto Read(lv_indev_data_t* data) -> void;
auto registration() -> lv_indev_t* { return registration_; }
+ auto mode(drivers::NvsStorage::InputModes mode) { mode_ = mode; }
auto lock(bool l) -> void { is_locked_ = l; }
private:
@@ -40,8 +46,41 @@ class EncoderInput {
drivers::IGpios& gpios_;
drivers::TouchWheel& raw_wheel_;
std::unique_ptr<drivers::RelativeWheel> relative_wheel_;
+ std::unique_ptr<Scroller> scroller_;
+ drivers::NvsStorage::InputModes mode_;
bool is_locked_;
+
+ enum class Keys {
+ kVolumeUp,
+ kVolumeDown,
+ kTouchWheel,
+ kTouchWheelCenter,
+ kDirectionalUp,
+ kDirectionalRight,
+ kDirectionalDown,
+ kDirectionalLeft,
+ };
+
+ std::unordered_map<Keys, uint64_t> touch_time_ms_;
+ std::unordered_map<Keys, bool> short_press_fired_;
+ std::unordered_map<Keys, bool> long_press_fired_;
+
+ 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;
+};
+
+class Scroller {
+ public:
+ Scroller() : last_input_ms_(0), velocity_(0) {}
+
+ auto AddInput(uint64_t, int) -> int;
+
+ private:
+ uint64_t last_input_ms_;
+ int velocity_;
};
} // namespace ui