summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTab_theFox <tab_thefox@noreply.codeberg.org>2025-03-23 17:45:19 +0100
committerTab_theFox <tab_thefox@noreply.codeberg.org>2025-04-22 22:23:47 +0200
commitdc5676229d19f317b97df6a3d3582bbb02df33df (patch)
tree2036ecb5b6fdefaabc6beb9ac7cab21dcadcad56 /src
parent79c9f0b6ccfec97f23563010f134d842c4240582 (diff)
downloadtangara-fw-dc5676229d19f317b97df6a3d3582bbb02df33df.tar.gz
add single touch shourtcuts for touch wheel
This allows you to use the touch wheel for quick playback control: - bottom quadrant for play/pause - left and right quadrant for track skip back / forward - top quadrant to seek back 25 seconds (handy when listening to a podcast)
Diffstat (limited to 'src')
-rw-r--r--src/drivers/include/drivers/nvs.hpp1
-rw-r--r--src/drivers/nvs.cpp2
-rw-r--r--src/tangara/input/device_factory.cpp17
-rw-r--r--src/tangara/input/input_hook_actions.cpp6
-rw-r--r--src/tangara/input/input_hook_actions.hpp2
-rw-r--r--src/tangara/input/input_touch_wheel.cpp66
-rw-r--r--src/tangara/input/input_touch_wheel.hpp8
-rw-r--r--src/tangara/input/lvgl_input_driver.cpp2
-rw-r--r--src/tangara/lua/lua_controls.cpp6
-rw-r--r--src/tangara/ui/ui_events.hpp4
-rw-r--r--src/tangara/ui/ui_fsm.cpp25
-rw-r--r--src/tangara/ui/ui_fsm.hpp2
12 files changed, 123 insertions, 18 deletions
diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp
index b490ac3d..6fc61131 100644
--- a/src/drivers/include/drivers/nvs.hpp
+++ b/src/drivers/include/drivers/nvs.hpp
@@ -147,6 +147,7 @@ class NvsStorage {
kDisabled = 0,
kDirectionalWheel = 1,
kRotatingWheel = 2,
+ kWheelWithButtons = 3,
};
auto WheelInput() -> WheelInputModes;
diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp
index 64ed9c1a..d02c80e2 100644
--- a/src/drivers/nvs.cpp
+++ b/src/drivers/nvs.cpp
@@ -628,6 +628,8 @@ auto NvsStorage::WheelInput() -> WheelInputModes {
return WheelInputModes::kDirectionalWheel;
case static_cast<uint8_t>(WheelInputModes::kRotatingWheel):
return WheelInputModes::kRotatingWheel;
+ case static_cast<uint8_t>(WheelInputModes::kWheelWithButtons):
+ return WheelInputModes::kWheelWithButtons;
default:
return WheelInputModes::kRotatingWheel;
}
diff --git a/src/tangara/input/device_factory.cpp b/src/tangara/input/device_factory.cpp
index ff597556..9ae69e47 100644
--- a/src/tangara/input/device_factory.cpp
+++ b/src/tangara/input/device_factory.cpp
@@ -25,7 +25,11 @@ DeviceFactory::DeviceFactory(
: services_(services) {
if (services->touchwheel()) {
wheel_ =
- std::make_shared<TouchWheel>(services->nvs(), **services->touchwheel());
+ std::make_shared<TouchWheel>(services->nvs(), **services->touchwheel(), services->track_queue());
+ auto wheel_mode = services_->nvs().WheelInput();
+ if (wheel_mode == drivers::NvsStorage::WheelInputModes::kWheelWithButtons) {
+ wheel_->activate_buttons(true);
+ }
}
reset_ = std::make_shared<HardReset>(services_->gpios());
}
@@ -73,6 +77,17 @@ auto DeviceFactory::createInputs()
}
break;
case drivers::NvsStorage::WheelInputModes::kRotatingWheel:
+ if (wheel_) {
+ wheel_->activate_buttons(false);
+ ret.push_back(wheel_);
+ }
+ break;
+ case drivers::NvsStorage::WheelInputModes::kWheelWithButtons:
+ if (wheel_) {
+ wheel_->activate_buttons(true);
+ ret.push_back(wheel_);
+ }
+ break;
default: // Don't break input over a bad enum value.
if (wheel_) {
ret.push_back(wheel_);
diff --git a/src/tangara/input/input_hook_actions.cpp b/src/tangara/input/input_hook_actions.cpp
index 001d3fb0..587683ba 100644
--- a/src/tangara/input/input_hook_actions.cpp
+++ b/src/tangara/input/input_hook_actions.cpp
@@ -68,6 +68,12 @@ auto nextTrack(audio::TrackQueue& queue) -> HookCallback {
}};
}
+auto skipBack() -> HookCallback {
+ return HookCallback{.name = "skip_back", .fn = [&](lv_indev_data_t* d) {
+ events::Ui().Dispatch(ui::SeekBack{.seconds = 25});
+ }};
+}
+
auto prevTrack(audio::TrackQueue& queue) -> HookCallback {
return HookCallback{.name = "prev_track", .fn = [&](lv_indev_data_t* d) {
queue.previous();
diff --git a/src/tangara/input/input_hook_actions.hpp b/src/tangara/input/input_hook_actions.hpp
index 2db0b6e7..67902924 100644
--- a/src/tangara/input/input_hook_actions.hpp
+++ b/src/tangara/input/input_hook_actions.hpp
@@ -27,6 +27,8 @@ auto togglePlayPause() -> HookCallback;
auto nextTrack(audio::TrackQueue& queue) -> HookCallback;
auto prevTrack(audio::TrackQueue& queue) -> HookCallback;
+auto skipBack() -> HookCallback;
+
auto volumeUp() -> HookCallback;
auto volumeDown() -> HookCallback;
diff --git a/src/tangara/input/input_touch_wheel.cpp b/src/tangara/input/input_touch_wheel.cpp
index ae192e31..0a8bab48 100644
--- a/src/tangara/input/input_touch_wheel.cpp
+++ b/src/tangara/input/input_touch_wheel.cpp
@@ -25,9 +25,12 @@
namespace input {
-TouchWheel::TouchWheel(drivers::NvsStorage& nvs, drivers::TouchWheel& wheel)
+TouchWheel::TouchWheel(drivers::NvsStorage& nvs,
+ drivers::TouchWheel& wheel,
+ audio::TrackQueue& queue)
: nvs_(nvs),
wheel_(wheel),
+ queue_(queue),
sensitivity_(static_cast<int>(nvs.ScrollSensitivity()),
[&](const lua::LuaValue& val) {
if (!std::holds_alternative<int>(val)) {
@@ -55,7 +58,23 @@ TouchWheel::TouchWheel(drivers::NvsStorage& nvs, drivers::TouchWheel& wheel)
threshold_(calculateThreshold(nvs.ScrollSensitivity())),
is_first_read_(true),
last_angle_(0),
- last_wheel_touch_time_(0) {}
+ last_wheel_touch_time_(0),
+ buttons_active_(false) {}
+
+auto TouchWheel::activate_buttons(bool activate) -> void {
+ if (activate) {
+ up_.override(Trigger::State::kClick, {actions::skipBack()});
+ left_.override(Trigger::State::kClick, {actions::prevTrack(queue_)});
+ right_.override(Trigger::State::kClick, {actions::nextTrack(queue_)});
+ down_.override(Trigger::State::kClick, {actions::togglePlayPause()});
+ } else {
+ up_.override(Trigger::State::kClick, std::nullopt);
+ left_.override(Trigger::State::kClick, std::nullopt);
+ right_.override(Trigger::State::kClick, std::nullopt);
+ down_.override(Trigger::State::kClick, std::nullopt);
+ }
+ buttons_active_ = activate;
+}
auto TouchWheel::read(lv_indev_data_t* data, std::vector<InputEvent>& events) -> void {
if (locked_) {
@@ -99,25 +118,42 @@ auto TouchWheel::read(lv_indev_data_t* data, std::vector<InputEvent>& events) ->
// If the user is touching the wheel but not scrolling, then they may be
// clicking on one of the wheel's cardinal directions.
+ const uint64_t now_us = esp_timer_get_time() / 1000;
if (is_scrolling_) {
up_.cancel();
right_.cancel();
down_.cancel();
left_.cancel();
+ last_scroll_ = now_us;
} else {
- bool pressing = wheel_data.is_wheel_touched;
- up_.update(pressing && drivers::TouchWheel::isAngleWithin(
- wheel_data.wheel_position, 0, 32),
- data);
- right_.update(pressing && drivers::TouchWheel::isAngleWithin(
- wheel_data.wheel_position, 192, 32),
- data);
- down_.update(pressing && drivers::TouchWheel::isAngleWithin(
- wheel_data.wheel_position, 128, 32),
- data);
- left_.update(pressing && drivers::TouchWheel::isAngleWithin(
- wheel_data.wheel_position, 64, 32),
- data);
+ const auto time_since_last_scroll = last_scroll_ - now_us;
+ bool pressing = wheel_data.is_wheel_touched &&
+ (time_since_last_scroll > SCROLL_TIMEOUT_US);
+
+ const auto ustate =
+ up_.update(pressing && drivers::TouchWheel::isAngleWithin(
+ wheel_data.wheel_position, 0, 32),
+ data);
+ const auto rstate =
+ right_.update(pressing && drivers::TouchWheel::isAngleWithin(
+ wheel_data.wheel_position, 192, 32),
+ data);
+ const auto dstate =
+ down_.update(pressing && drivers::TouchWheel::isAngleWithin(
+ wheel_data.wheel_position, 128, 32),
+ data);
+ const auto lstate =
+ left_.update(pressing && drivers::TouchWheel::isAngleWithin(
+ wheel_data.wheel_position, 64, 32),
+ data);
+
+ // This is for haptic feedback.
+ if (buttons_active_ &&
+ (ustate == Trigger::State::kClick || rstate == Trigger::State::kClick ||
+ dstate == Trigger::State::kClick ||
+ lstate == Trigger::State::kClick)) {
+ events.push_back(InputEvent::kOnPress);
+ }
}
}
diff --git a/src/tangara/input/input_touch_wheel.hpp b/src/tangara/input/input_touch_wheel.hpp
index 3f2ed49e..9c9135b4 100644
--- a/src/tangara/input/input_touch_wheel.hpp
+++ b/src/tangara/input/input_touch_wheel.hpp
@@ -23,7 +23,7 @@ namespace input {
class TouchWheel : public IInputDevice {
public:
- TouchWheel(drivers::NvsStorage&, drivers::TouchWheel&);
+ TouchWheel(drivers::NvsStorage&, drivers::TouchWheel&, audio::TrackQueue&);
auto read(lv_indev_data_t* data, std::vector<InputEvent>& events) -> void override;
@@ -35,6 +35,8 @@ class TouchWheel : public IInputDevice {
auto sensitivity() -> lua::Property&;
+ auto activate_buttons(bool activate) -> void;
+
private:
const int64_t SCROLL_TIMEOUT_US = 250000; // 250ms
@@ -44,6 +46,8 @@ class TouchWheel : public IInputDevice {
drivers::NvsStorage& nvs_;
drivers::TouchWheel& wheel_;
+ audio::TrackQueue& queue_;
+
lua::Property sensitivity_;
TriggerHooks centre_;
@@ -54,10 +58,12 @@ class TouchWheel : public IInputDevice {
bool locked_;
bool is_scrolling_;
+ uint64_t last_scroll_;
uint8_t threshold_;
bool is_first_read_;
uint8_t last_angle_;
int64_t last_wheel_touch_time_;
+ bool buttons_active_;
};
} // namespace input
diff --git a/src/tangara/input/lvgl_input_driver.cpp b/src/tangara/input/lvgl_input_driver.cpp
index b4bab365..00cc2b81 100644
--- a/src/tangara/input/lvgl_input_driver.cpp
+++ b/src/tangara/input/lvgl_input_driver.cpp
@@ -64,6 +64,8 @@ auto intToWheelMode(int raw)
return drivers::NvsStorage::WheelInputModes::kDirectionalWheel;
case 2:
return drivers::NvsStorage::WheelInputModes::kRotatingWheel;
+ case 3:
+ return drivers::NvsStorage::WheelInputModes::kWheelWithButtons;
default:
return {};
}
diff --git a/src/tangara/lua/lua_controls.cpp b/src/tangara/lua/lua_controls.cpp
index 69053f43..f1305c55 100644
--- a/src/tangara/lua/lua_controls.cpp
+++ b/src/tangara/lua/lua_controls.cpp
@@ -36,7 +36,11 @@ static auto wheel_schemes(lua_State* L) -> int {
lua_pushliteral(L, "Touchwheel");
lua_rawseti(
- L, -2, static_cast<int>(drivers::NvsStorage::WheelInputModes::kRotatingWheel));
+ L, -2, static_cast<int>(drivers::NvsStorage::WheelInputModes::kRotatingWheel));
+
+ lua_pushliteral(L, "Wheel with Buttons");
+ lua_rawseti(L, -2,
+ static_cast<int>(drivers::NvsStorage::WheelInputModes::kWheelWithButtons));
return 1;
}
diff --git a/src/tangara/ui/ui_events.hpp b/src/tangara/ui/ui_events.hpp
index 0f371769..ce9320fc 100644
--- a/src/tangara/ui/ui_events.hpp
+++ b/src/tangara/ui/ui_events.hpp
@@ -34,6 +34,10 @@ struct Screenshot : tinyfsm::Event {
std::string filename;
};
+struct SeekBack : tinyfsm::Event {
+ uint32_t seconds;
+};
+
namespace internal {
struct InitDisplay : tinyfsm::Event {
diff --git a/src/tangara/ui/ui_fsm.cpp b/src/tangara/ui/ui_fsm.cpp
index aaf35d13..1b8c0984 100644
--- a/src/tangara/ui/ui_fsm.cpp
+++ b/src/tangara/ui/ui_fsm.cpp
@@ -403,6 +403,31 @@ void UiState::react(const Screenshot& ev) {
SaveScreenshot(sCurrentScreen->root(), ev.filename);
}
+void UiState::react(const SeekBack& ev) {
+ const auto playback_position = sPlaybackPosition.get();
+ if (!std::holds_alternative<int>(playback_position)) {
+ // I don't think this ever happens, but check anyway.
+ return;
+ }
+
+ const auto track = sPlaybackTrack.get();
+ if (!std::holds_alternative<audio::TrackInfo>(track)) {
+ // Nothing is playing
+ return;
+ }
+
+ const auto current_position = std::get<int>(playback_position);
+ int32_t seek_position = current_position - ev.seconds;
+ if (seek_position < 1) {
+ seek_position = 0;
+ }
+
+ events::Audio().Dispatch(audio::SetTrack{
+ .new_track = std::get<audio::TrackInfo>(track).uri,
+ .seek_to_second = seek_position,
+ });
+}
+
void UiState::react(const system_fsm::KeyLockChanged& ev) {
sDisplay->SetDisplayOn(!ev.locking);
sInput->lock(ev.locking);
diff --git a/src/tangara/ui/ui_fsm.hpp b/src/tangara/ui/ui_fsm.hpp
index d4354bec..01f0beb1 100644
--- a/src/tangara/ui/ui_fsm.hpp
+++ b/src/tangara/ui/ui_fsm.hpp
@@ -56,6 +56,8 @@ class UiState : public tinyfsm::Fsm<UiState> {
void react(const tinyfsm::Event& ev) {}
void react(const Screenshot&);
+ void react(const SeekBack&);
+
virtual void react(const OnLuaError&) {}
virtual void react(const DumpLuaStack&) {}
virtual void react(const internal::BackPressed&) {}