|
|
@ -25,7 +25,7 @@ |
|
|
|
|
|
|
|
|
|
|
|
[[maybe_unused]] static constexpr char kTag[] = "input"; |
|
|
|
[[maybe_unused]] static constexpr char kTag[] = "input"; |
|
|
|
|
|
|
|
|
|
|
|
constexpr int kDPadAngleThreshold = 20; |
|
|
|
constexpr int kDPadAngleThreshold = 10; |
|
|
|
constexpr int kLongPressDelayMs = 500; |
|
|
|
constexpr int kLongPressDelayMs = 500; |
|
|
|
constexpr int kRepeatDelayMs = 250; |
|
|
|
constexpr int kRepeatDelayMs = 250; |
|
|
|
|
|
|
|
|
|
|
@ -49,7 +49,8 @@ EncoderInput::EncoderInput(drivers::IGpios& gpios, drivers::TouchWheel& wheel) |
|
|
|
relative_wheel_(std::make_unique<drivers::RelativeWheel>(wheel)), |
|
|
|
relative_wheel_(std::make_unique<drivers::RelativeWheel>(wheel)), |
|
|
|
scroller_(std::make_unique<Scroller>()), |
|
|
|
scroller_(std::make_unique<Scroller>()), |
|
|
|
mode_(drivers::NvsStorage::InputModes::kRotatingWheel), |
|
|
|
mode_(drivers::NvsStorage::InputModes::kRotatingWheel), |
|
|
|
is_locked_(false) { |
|
|
|
is_locked_(false), |
|
|
|
|
|
|
|
is_scrolling_wheel_(false) { |
|
|
|
lv_indev_drv_init(&driver_); |
|
|
|
lv_indev_drv_init(&driver_); |
|
|
|
driver_.type = LV_INDEV_TYPE_ENCODER; |
|
|
|
driver_.type = LV_INDEV_TYPE_ENCODER; |
|
|
|
driver_.read_cb = encoder_read; |
|
|
|
driver_.read_cb = encoder_read; |
|
|
@ -70,7 +71,7 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { |
|
|
|
|
|
|
|
|
|
|
|
raw_wheel_.Update(); |
|
|
|
raw_wheel_.Update(); |
|
|
|
relative_wheel_->Update(); |
|
|
|
relative_wheel_->Update(); |
|
|
|
// GPIOs updating is handled by system_fsm.
|
|
|
|
// GPIO (for volume buttons) updating is handled by system_fsm.
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t now_ms = esp_timer_get_time() / 1000; |
|
|
|
uint64_t now_ms = esp_timer_get_time() / 1000; |
|
|
|
|
|
|
|
|
|
|
@ -83,36 +84,50 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Check each button.
|
|
|
|
// Check each button.
|
|
|
|
HandleKeyState(Keys::kVolumeUp, now_ms, |
|
|
|
UpdateKeyState(Keys::kVolumeUp, now_ms, |
|
|
|
!gpios_.Get(drivers::IGpios::Pin::kKeyUp)); |
|
|
|
!gpios_.Get(drivers::IGpios::Pin::kKeyUp)); |
|
|
|
HandleKeyState(Keys::kVolumeDown, now_ms, |
|
|
|
UpdateKeyState(Keys::kVolumeDown, now_ms, |
|
|
|
!gpios_.Get(drivers::IGpios::Pin::kKeyDown)); |
|
|
|
!gpios_.Get(drivers::IGpios::Pin::kKeyDown)); |
|
|
|
|
|
|
|
|
|
|
|
drivers::TouchWheelData wheel_data = raw_wheel_.GetTouchWheelData(); |
|
|
|
drivers::TouchWheelData wheel_data = raw_wheel_.GetTouchWheelData(); |
|
|
|
HandleKeyState(Keys::kTouchWheel, now_ms, wheel_data.is_wheel_touched); |
|
|
|
UpdateKeyState(Keys::kTouchWheel, now_ms, wheel_data.is_wheel_touched); |
|
|
|
HandleKeyState(Keys::kTouchWheelCenter, now_ms, wheel_data.is_button_touched); |
|
|
|
UpdateKeyState(Keys::kTouchWheelCenter, now_ms, wheel_data.is_button_touched); |
|
|
|
|
|
|
|
|
|
|
|
HandleKeyState( |
|
|
|
UpdateKeyState( |
|
|
|
Keys::kDirectionalUp, now_ms, |
|
|
|
Keys::kDirectionalUp, now_ms, |
|
|
|
wheel_data.is_wheel_touched && |
|
|
|
wheel_data.is_wheel_touched && |
|
|
|
IsAngleWithin(wheel_data.wheel_position, 0, kDPadAngleThreshold)); |
|
|
|
IsAngleWithin(wheel_data.wheel_position, 0, kDPadAngleThreshold)); |
|
|
|
HandleKeyState( |
|
|
|
UpdateKeyState( |
|
|
|
Keys::kDirectionalLeft, now_ms, |
|
|
|
Keys::kDirectionalLeft, now_ms, |
|
|
|
wheel_data.is_wheel_touched && |
|
|
|
wheel_data.is_wheel_touched && |
|
|
|
IsAngleWithin(wheel_data.wheel_position, 63, kDPadAngleThreshold)); |
|
|
|
IsAngleWithin(wheel_data.wheel_position, 63, kDPadAngleThreshold)); |
|
|
|
HandleKeyState( |
|
|
|
UpdateKeyState( |
|
|
|
Keys::kDirectionalDown, now_ms, |
|
|
|
Keys::kDirectionalDown, now_ms, |
|
|
|
wheel_data.is_wheel_touched && |
|
|
|
wheel_data.is_wheel_touched && |
|
|
|
IsAngleWithin(wheel_data.wheel_position, 127, kDPadAngleThreshold)); |
|
|
|
IsAngleWithin(wheel_data.wheel_position, 127, kDPadAngleThreshold)); |
|
|
|
HandleKeyState( |
|
|
|
UpdateKeyState( |
|
|
|
Keys::kDirectionalRight, now_ms, |
|
|
|
Keys::kDirectionalRight, now_ms, |
|
|
|
wheel_data.is_wheel_touched && |
|
|
|
wheel_data.is_wheel_touched && |
|
|
|
IsAngleWithin(wheel_data.wheel_position, 189, kDPadAngleThreshold)); |
|
|
|
IsAngleWithin(wheel_data.wheel_position, 189, kDPadAngleThreshold)); |
|
|
|
|
|
|
|
|
|
|
|
// We now have enough information to give LVGL its update.
|
|
|
|
// When the wheel is being scrolled, we want to ensure that other inputs
|
|
|
|
|
|
|
|
// involving the touchwheel don't trigger. This guards again two main issues:
|
|
|
|
|
|
|
|
// - hesitating when your thumb is on a cardinal direction, causing an
|
|
|
|
|
|
|
|
// unintentional long-press,
|
|
|
|
|
|
|
|
// - drifting from the outside of the wheel in a way that causes the centre
|
|
|
|
|
|
|
|
// key to be triggered.
|
|
|
|
|
|
|
|
if (is_scrolling_wheel_) { |
|
|
|
|
|
|
|
UpdateKeyState(Keys::kTouchWheelCenter, now_ms, false); |
|
|
|
|
|
|
|
UpdateKeyState(Keys::kDirectionalUp, now_ms, false); |
|
|
|
|
|
|
|
UpdateKeyState(Keys::kDirectionalLeft, now_ms, false); |
|
|
|
|
|
|
|
UpdateKeyState(Keys::kDirectionalDown, now_ms, false); |
|
|
|
|
|
|
|
UpdateKeyState(Keys::kDirectionalRight, now_ms, false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Now that we've determined the correct state for all keys, we can start
|
|
|
|
|
|
|
|
// mapping key states into actions, depending on the current control scheme.
|
|
|
|
|
|
|
|
if (mode_ == drivers::NvsStorage::InputModes::kButtonsOnly) { |
|
|
|
Trigger trigger; |
|
|
|
Trigger trigger; |
|
|
|
switch (mode_) { |
|
|
|
|
|
|
|
case drivers::NvsStorage::InputModes::kButtonsOnly: |
|
|
|
|
|
|
|
data->state = LV_INDEV_STATE_RELEASED; |
|
|
|
data->state = LV_INDEV_STATE_RELEASED; |
|
|
|
|
|
|
|
|
|
|
|
trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kLongPress, now_ms); |
|
|
|
trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kLongPress, now_ms); |
|
|
@ -138,33 +153,9 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { |
|
|
|
data->state = LV_INDEV_STATE_PRESSED; |
|
|
|
data->state = LV_INDEV_STATE_PRESSED; |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} else if (mode_ == drivers::NvsStorage::InputModes::kDirectionalWheel) { |
|
|
|
break; |
|
|
|
Trigger trigger; |
|
|
|
case drivers::NvsStorage::InputModes::kButtonsWithWheel: |
|
|
|
trigger = TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, 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); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case drivers::NvsStorage::InputModes::kDirectionalWheel: |
|
|
|
|
|
|
|
trigger = |
|
|
|
|
|
|
|
TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, now_ms); |
|
|
|
|
|
|
|
data->state = trigger == Trigger::kClick ? LV_INDEV_STATE_PRESSED |
|
|
|
data->state = trigger == Trigger::kClick ? LV_INDEV_STATE_PRESSED |
|
|
|
: LV_INDEV_STATE_RELEASED; |
|
|
|
: LV_INDEV_STATE_RELEASED; |
|
|
|
|
|
|
|
|
|
|
@ -215,18 +206,22 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { |
|
|
|
case Trigger::kLongPress: |
|
|
|
case Trigger::kLongPress: |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} else if (mode_ == drivers::NvsStorage::InputModes::kRotatingWheel) { |
|
|
|
break; |
|
|
|
|
|
|
|
case drivers::NvsStorage::InputModes::kRotatingWheel: |
|
|
|
|
|
|
|
if (!raw_wheel_.GetTouchWheelData().is_wheel_touched) { |
|
|
|
if (!raw_wheel_.GetTouchWheelData().is_wheel_touched) { |
|
|
|
|
|
|
|
// User has released the wheel.
|
|
|
|
|
|
|
|
is_scrolling_wheel_ = false; |
|
|
|
data->enc_diff = scroller_->AddInput(now_ms, 0); |
|
|
|
data->enc_diff = scroller_->AddInput(now_ms, 0); |
|
|
|
} else if (relative_wheel_->ticks() != 0) { |
|
|
|
} else if (relative_wheel_->ticks() != 0) { |
|
|
|
|
|
|
|
// User is touching the wheel, and has just passed the sensitivity
|
|
|
|
|
|
|
|
// threshold for a scroll tick.
|
|
|
|
|
|
|
|
is_scrolling_wheel_ = true; |
|
|
|
data->enc_diff = scroller_->AddInput(now_ms, relative_wheel_->ticks()); |
|
|
|
data->enc_diff = scroller_->AddInput(now_ms, relative_wheel_->ticks()); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
|
|
|
|
// User is touching the wheel, but hasn't moved.
|
|
|
|
data->enc_diff = 0; |
|
|
|
data->enc_diff = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
trigger = |
|
|
|
Trigger trigger = |
|
|
|
TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, now_ms); |
|
|
|
TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, now_ms); |
|
|
|
switch (trigger) { |
|
|
|
switch (trigger) { |
|
|
|
case Trigger::kNone: |
|
|
|
case Trigger::kNone: |
|
|
@ -264,15 +259,7 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Only trigger the directional long-press gestures if they trigger at the
|
|
|
|
trigger = TriggerKey(Keys::kDirectionalLeft, KeyStyle::kLongPress, now_ms); |
|
|
|
// same time as a trigger on the overall touchwheel. This means the
|
|
|
|
|
|
|
|
// gestures only trigger if it's your only interaction with the wheel this
|
|
|
|
|
|
|
|
// press; scrolling and then resting on a direction should not trigger
|
|
|
|
|
|
|
|
// them.
|
|
|
|
|
|
|
|
trigger = TriggerKey(Keys::kTouchWheel, KeyStyle::kLongPress, now_ms); |
|
|
|
|
|
|
|
if (trigger == Trigger::kLongPress) { |
|
|
|
|
|
|
|
trigger = |
|
|
|
|
|
|
|
TriggerKey(Keys::kDirectionalLeft, KeyStyle::kLongPress, now_ms); |
|
|
|
|
|
|
|
switch (trigger) { |
|
|
|
switch (trigger) { |
|
|
|
case Trigger::kNone: |
|
|
|
case Trigger::kNone: |
|
|
|
break; |
|
|
|
break; |
|
|
@ -283,15 +270,12 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto EncoderInput::HandleKeyState(Keys key, uint64_t ms, bool clicked) -> void { |
|
|
|
auto EncoderInput::UpdateKeyState(Keys key, uint64_t ms, bool clicked) -> void { |
|
|
|
if (clicked) { |
|
|
|
if (clicked) { |
|
|
|
if (!touch_time_ms_.contains(key)) { |
|
|
|
if (!touch_time_ms_.contains(key)) { |
|
|
|
// Key was just pressed
|
|
|
|
// Key was just clicked.
|
|
|
|
touch_time_ms_[key] = ms; |
|
|
|
touch_time_ms_[key] = ms; |
|
|
|
just_released_.erase(key); |
|
|
|
just_released_.erase(key); |
|
|
|
fired_.erase(key); |
|
|
|
fired_.erase(key); |
|
|
|