diff --git a/lua/main.lua b/lua/main.lua index 12705c00..5be79c9a 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -74,7 +74,7 @@ local function init_ui() end end end), - controls.scheme:bind(function() + controls.wheel_scheme:bind(function() -- Set up a shortcut for jumping straight to the 'now playing' screen. -- Implemented as a binding so that the shortcut is still applied even if -- the control scheme is changed at runtime. diff --git a/lua/settings.lua b/lua/settings.lua index 5ed93521..d60ecfda 100644 --- a/lua/settings.lua +++ b/lua/settings.lua @@ -471,20 +471,33 @@ settings.InputSettings = SettingsScreen:new { controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() local option = controls_chooser:get('selected') local scheme = option_to_scheme[option] - control_scheme:set(scheme) + local prev_scheme = control_scheme:get() + -- Check the new scheme is valid + if not control_scheme:set(scheme) then + widgets.PopUp("Controls not valid") + control_scheme:set(prev_scheme) + end end) return controls_chooser end theme.set_subject(self.content:Label { - text = "Control scheme", + text = "Wheel Controls", }, "settings_title") - local controls_chooser = make_scheme_control(self, controls.schemes(), controls.scheme) + local controls_chooser = make_scheme_control(self, controls.wheel_schemes(), controls.wheel_scheme) local controls_chooser_desc = widgets.Description(controls_chooser, "Control scheme") theme.set_subject(self.content:Label { - text = "Control scheme when locked", + text = "Side Button Controls", + }, "settings_title") + make_scheme_control(self, controls.button_schemes(), controls.button_scheme) + + theme.set_subject(self.content:Label { + text = "Side Button Controls When Locked", + w = lvgl.PCT(80), + h = lvgl.SIZE_CONTENT, + long_mode = lvgl.LABEL.LONG_WRAP, }, "settings_title") local controls_locked = make_scheme_control(self, controls.locked_schemes(), controls.locked_scheme) local controls_locked_desc = widgets.Description(controls_locked, "Control scheme when locked") @@ -510,6 +523,14 @@ settings.InputSettings = SettingsScreen:new { controls.scroll_sensitivity:set(sensitivity:value() * slider_scale) end) local sensitivity_desc = widgets.Description(sensitivity, "Scroll Sensitivity") + + local spacer = self.content:Object { + w = lvgl.PCT(90), + h = 10, + } + sensitivity:onevent(lvgl.EVENT.FOCUSED, function() + spacer:scroll_to_view(true) + end) end } diff --git a/lua/widgets.lua b/lua/widgets.lua index de2aa43d..5e2ed858 100644 --- a/lua/widgets.lua +++ b/lua/widgets.lua @@ -372,4 +372,30 @@ function widgets.InfiniteList(parent, iterator, opts) return infinite_list end +function widgets.PopUp(text) + require("alerts").show(function() + local container = lvgl.Object(nil, { + w = lvgl.PCT(80), + h = lvgl.SIZE_CONTENT, + flex = { + flex_direction = "column", + justify_content = "center", + align_items = "center", + align_content = "center", + }, + radius = 8, + pad_all = 5, + }) + theme.set_subject(container, "pop_up") + container:Label { + text = text, + text_font = font.fusion_10, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + long_mode = lvgl.LABEL.LONG_WRAP, + } + container:center() + end) +end + return widgets diff --git a/luals-stubs/controls.lua b/luals-stubs/controls.lua index a95bc64f..0a8f4baa 100644 --- a/luals-stubs/controls.lua +++ b/luals-stubs/controls.lua @@ -8,7 +8,9 @@ --- controls. These controls include the touchwheel, the lock switch, and the --- side buttons. --- @class controls ---- @field scheme Property The currently configured control scheme +--- @field wheel_scheme Property The currently configured control scheme for the touchwheel +--- @field button_scheme Property The currently configured control scheme for the side buttons +--- @field locked_scheme Property The currently configured control scheme for the side buttons when locked --- @field scroll_sensitivity Property How much rotational motion is required on the touchwheel per scroll tick. --- @field lock_switch Property The current state of the device's lock switch. --- @field hooks funtion Returns a table containing the inputs and actions associated with the current control scheme. diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp index 7ef1fbf7..b490ac3d 100644 --- a/src/drivers/include/drivers/nvs.hpp +++ b/src/drivers/include/drivers/nvs.hpp @@ -143,23 +143,27 @@ class NvsStorage { auto AmpLeftBias() -> int_fast8_t; auto AmpLeftBias(int_fast8_t) -> void; - enum class InputModes : uint8_t { - kButtonsOnly = 0, - kButtonsWithWheel = 1, - kDirectionalWheel = 2, - kRotatingWheel = 3, + enum class WheelInputModes : uint8_t { + kDisabled = 0, + kDirectionalWheel = 1, + kRotatingWheel = 2, }; - auto PrimaryInput() -> InputModes; - auto PrimaryInput(InputModes) -> void; + auto WheelInput() -> WheelInputModes; + auto WheelInput(WheelInputModes) -> void; - enum class LockedInputModes : uint8_t { + enum class ButtonInputModes : uint8_t { kDisabled = 0, kVolumeOnly = 1, + kMediaControls = 2, + kNavigation = 3, }; - auto LockedInput() -> LockedInputModes; - auto LockedInput(LockedInputModes) -> void; + auto ButtonInput() -> ButtonInputModes; + auto ButtonInput(ButtonInputModes) -> void; + + auto LockedInput() -> ButtonInputModes; + auto LockedInput(ButtonInputModes) -> void; auto QueueRepeatMode() -> uint8_t; auto QueueRepeatMode(uint8_t) -> void; @@ -191,7 +195,8 @@ class NvsStorage { Setting amp_max_vol_; Setting amp_cur_vol_; Setting amp_left_bias_; - Setting input_mode_; + Setting wheel_input_mode_; + Setting button_input_mode_; Setting locked_input_mode_; Setting output_mode_; Setting haptics_mode_; diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp index 374c71d6..64ed9c1a 100644 --- a/src/drivers/nvs.cpp +++ b/src/drivers/nvs.cpp @@ -34,7 +34,8 @@ static constexpr char kKeyInterfaceTheme[] = "ui_theme"; static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max"; static constexpr char kKeyAmpCurrentVolume[] = "hp_vol"; static constexpr char kKeyAmpLeftBias[] = "hp_bias"; -static constexpr char kKeyPrimaryInput[] = "in_pri"; +static constexpr char kKeyWheelInput[] = "in_wheel"; +static constexpr char kKeyButtonInput[] = "in_btn"; static constexpr char kKeyLockedInput[] = "in_locked"; static constexpr char kKeyHaptics[] = "haptic_mode"; static constexpr char kKeyScrollSensitivity[] = "scroll"; @@ -277,7 +278,8 @@ NvsStorage::NvsStorage(nvs_handle_t handle) amp_max_vol_(kKeyAmpMaxVolume), amp_cur_vol_(kKeyAmpCurrentVolume), amp_left_bias_(kKeyAmpLeftBias), - input_mode_(kKeyPrimaryInput), + wheel_input_mode_(kKeyWheelInput), + button_input_mode_(kKeyButtonInput), locked_input_mode_(kKeyLockedInput), output_mode_(kKeyOutput), haptics_mode_(kKeyHaptics), @@ -309,7 +311,8 @@ auto NvsStorage::Read() -> void { amp_max_vol_.read(handle_); amp_cur_vol_.read(handle_); amp_left_bias_.read(handle_); - input_mode_.read(handle_); + wheel_input_mode_.read(handle_); + button_input_mode_.read(handle_); locked_input_mode_.read(handle_); output_mode_.read(handle_); haptics_mode_.read(handle_); @@ -336,7 +339,8 @@ auto NvsStorage::Write() -> bool { amp_max_vol_.write(handle_); amp_cur_vol_.write(handle_); amp_left_bias_.write(handle_); - input_mode_.write(handle_); + wheel_input_mode_.write(handle_); + button_input_mode_.write(handle_); locked_input_mode_.write(handle_); output_mode_.write(handle_); haptics_mode_.write(handle_); @@ -615,40 +619,63 @@ auto NvsStorage::AmpLeftBias(int_fast8_t val) -> void { amp_left_bias_.set(val); } -auto NvsStorage::PrimaryInput() -> InputModes { +auto NvsStorage::WheelInput() -> WheelInputModes { std::lock_guard lock{mutex_}; - switch (input_mode_.get().value_or(3)) { - case static_cast(InputModes::kButtonsOnly): - return InputModes::kButtonsOnly; - case static_cast(InputModes::kButtonsWithWheel): - return InputModes::kButtonsWithWheel; - case static_cast(InputModes::kDirectionalWheel): - return InputModes::kDirectionalWheel; - case static_cast(InputModes::kRotatingWheel): - return InputModes::kRotatingWheel; + switch (wheel_input_mode_.get().value_or(3)) { + case static_cast(WheelInputModes::kDisabled): + return WheelInputModes::kDisabled; + case static_cast(WheelInputModes::kDirectionalWheel): + return WheelInputModes::kDirectionalWheel; + case static_cast(WheelInputModes::kRotatingWheel): + return WheelInputModes::kRotatingWheel; default: - return InputModes::kRotatingWheel; + return WheelInputModes::kRotatingWheel; } } -auto NvsStorage::PrimaryInput(InputModes mode) -> void { +auto NvsStorage::WheelInput(WheelInputModes mode) -> void { std::lock_guard lock{mutex_}; - input_mode_.set(static_cast(mode)); + wheel_input_mode_.set(static_cast(mode)); } -auto NvsStorage::LockedInput() -> LockedInputModes { +auto NvsStorage::ButtonInput() -> ButtonInputModes { std::lock_guard lock{mutex_}; - switch (locked_input_mode_.get().value_or(static_cast(LockedInputModes::kDisabled))) { - case static_cast(LockedInputModes::kDisabled): - return LockedInputModes::kDisabled; - case static_cast(LockedInputModes::kVolumeOnly): - return LockedInputModes::kVolumeOnly; + switch (button_input_mode_.get().value_or(static_cast(ButtonInputModes::kVolumeOnly))) { + case static_cast(ButtonInputModes::kDisabled): + return ButtonInputModes::kDisabled; + case static_cast(ButtonInputModes::kVolumeOnly): + return ButtonInputModes::kVolumeOnly; + case static_cast(ButtonInputModes::kMediaControls): + return ButtonInputModes::kMediaControls; + case static_cast(ButtonInputModes::kNavigation): + return ButtonInputModes::kNavigation; default: - return LockedInputModes::kDisabled; + return ButtonInputModes::kVolumeOnly; } } -auto NvsStorage::LockedInput(LockedInputModes mode) -> void { +auto NvsStorage::ButtonInput(ButtonInputModes mode) -> void { + std::lock_guard lock{mutex_}; + button_input_mode_.set(static_cast(mode)); +} + +auto NvsStorage::LockedInput() -> ButtonInputModes { + std::lock_guard lock{mutex_}; + switch (locked_input_mode_.get().value_or(static_cast(ButtonInputModes::kDisabled))) { + case static_cast(ButtonInputModes::kDisabled): + return ButtonInputModes::kDisabled; + case static_cast(ButtonInputModes::kVolumeOnly): + return ButtonInputModes::kVolumeOnly; + case static_cast(ButtonInputModes::kMediaControls): + return ButtonInputModes::kMediaControls; + case static_cast(ButtonInputModes::kNavigation): + return ButtonInputModes::kNavigation; + default: + return ButtonInputModes::kDisabled; + } +} + +auto NvsStorage::LockedInput(ButtonInputModes mode) -> void { std::lock_guard lock{mutex_}; locked_input_mode_.set(static_cast(mode)); } diff --git a/src/tangara/input/device_factory.cpp b/src/tangara/input/device_factory.cpp index fe2e2485..ff597556 100644 --- a/src/tangara/input/device_factory.cpp +++ b/src/tangara/input/device_factory.cpp @@ -16,6 +16,7 @@ #include "input/input_touch_dpad.hpp" #include "input/input_touch_wheel.hpp" #include "input/input_volume_buttons.hpp" +#include "input/input_media_buttons.hpp" namespace input { @@ -26,30 +27,73 @@ DeviceFactory::DeviceFactory( wheel_ = std::make_shared(services->nvs(), **services->touchwheel()); } + reset_ = std::make_shared(services_->gpios()); } -auto DeviceFactory::createInputs(drivers::NvsStorage::InputModes mode) +auto DeviceFactory::createLockedInputs() -> std::vector> { + auto locked_mode = services_->nvs().LockedInput(); std::vector> ret; - switch (mode) { - case drivers::NvsStorage::InputModes::kButtonsOnly: - ret.push_back(std::make_shared(services_->gpios())); + switch (locked_mode) { + case drivers::NvsStorage::ButtonInputModes::kDisabled: + break; + case drivers::NvsStorage::ButtonInputModes::kMediaControls: + ret.push_back(std::make_shared(services_->gpios(), services_->track_queue())); + break; + case drivers::NvsStorage::ButtonInputModes::kNavigation: + // I don't think we want navigation to work when locked? + // ret.push_back(std::make_shared(services_->gpios())); break; - case drivers::NvsStorage::InputModes::kDirectionalWheel: + case drivers::NvsStorage::ButtonInputModes::kVolumeOnly: ret.push_back(std::make_shared(services_->gpios())); + break; + } + ret.push_back(reset_); + return ret; +} + +auto DeviceFactory::createInputs() + -> std::vector> { + std::vector> ret; + auto wheel_mode = services_->nvs().WheelInput(); + auto buttons_mode = services_->nvs().ButtonInput(); + // Make sure we always have a navigational input + if (wheel_mode == drivers::NvsStorage::WheelInputModes::kDisabled) { + if (buttons_mode != drivers::NvsStorage::ButtonInputModes::kNavigation) { + wheel_mode = drivers::NvsStorage::WheelInputModes::kRotatingWheel; + services_->nvs().WheelInput(wheel_mode); + } + } + switch (wheel_mode) { + case drivers::NvsStorage::WheelInputModes::kDisabled: + break; + case drivers::NvsStorage::WheelInputModes::kDirectionalWheel: if (services_->touchwheel()) { ret.push_back(std::make_shared(**services_->touchwheel())); } break; - case drivers::NvsStorage::InputModes::kRotatingWheel: + case drivers::NvsStorage::WheelInputModes::kRotatingWheel: default: // Don't break input over a bad enum value. - ret.push_back(std::make_shared(services_->gpios())); if (wheel_) { ret.push_back(wheel_); } break; } - ret.push_back(std::make_shared(services_->gpios())); + switch (buttons_mode) { + case drivers::NvsStorage::ButtonInputModes::kDisabled: + break; + case drivers::NvsStorage::ButtonInputModes::kMediaControls: + ret.push_back(std::make_shared(services_->gpios(), services_->track_queue())); + break; + case drivers::NvsStorage::ButtonInputModes::kNavigation: + ret.push_back(std::make_shared(services_->gpios())); + break; + case drivers::NvsStorage::ButtonInputModes::kVolumeOnly: + default: + ret.push_back(std::make_shared(services_->gpios())); + break; + } + ret.push_back(reset_); return ret; } diff --git a/src/tangara/input/device_factory.hpp b/src/tangara/input/device_factory.hpp index 5044d025..1f95bbef 100644 --- a/src/tangara/input/device_factory.hpp +++ b/src/tangara/input/device_factory.hpp @@ -12,6 +12,7 @@ #include "input/feedback_device.hpp" #include "input/input_device.hpp" #include "input/input_touch_wheel.hpp" +#include "input/input_hard_reset.hpp" #include "drivers/nvs.hpp" #include "system_fsm/service_locator.hpp" @@ -21,7 +22,9 @@ class DeviceFactory { public: DeviceFactory(std::shared_ptr); - auto createInputs(drivers::NvsStorage::InputModes mode) + auto createInputs() + -> std::vector>; + auto createLockedInputs() -> std::vector>; auto createFeedbacks() -> std::vector>; @@ -34,6 +37,10 @@ class DeviceFactory { // HACK: the touchwheel is current a special case, since it's the only input // device that has some kind of setting/configuration; scroll sensitivity. std::shared_ptr wheel_; + + // Another special case, the hard reset input should persist between + // lock modes, and always be added to the created inputs + std::shared_ptr reset_; }; } // namespace input diff --git a/src/tangara/input/input_device.hpp b/src/tangara/input/input_device.hpp index 7b8d993d..183781bc 100644 --- a/src/tangara/input/input_device.hpp +++ b/src/tangara/input/input_device.hpp @@ -36,7 +36,7 @@ class IInputDevice { } /* Called by the LVGL driver when controls are being locked. */ - virtual auto onLock(drivers::NvsStorage::LockedInputModes) -> void {} + virtual auto onLock() -> void {} /* Called by the LVGL driver when controls are being unlocked. */ virtual auto onUnlock() -> void {} }; diff --git a/src/tangara/input/input_hook_actions.cpp b/src/tangara/input/input_hook_actions.cpp index fb6a677c..001d3fb0 100644 --- a/src/tangara/input/input_hook_actions.cpp +++ b/src/tangara/input/input_hook_actions.cpp @@ -62,11 +62,22 @@ auto volumeDown() -> HookCallback { }}; } -auto allActions() -> std::vector { - return { - select(), scrollUp(), scrollDown(), scrollToTop(), - scrollToBottom(), goBack(), volumeUp(), volumeDown(), - }; +auto nextTrack(audio::TrackQueue& queue) -> HookCallback { + return HookCallback{.name = "next_track", .fn = [&](lv_indev_data_t* d) { + queue.next(); + }}; +} + +auto prevTrack(audio::TrackQueue& queue) -> HookCallback { + return HookCallback{.name = "prev_track", .fn = [&](lv_indev_data_t* d) { + queue.previous(); + }}; +} + +auto togglePlayPause() -> HookCallback { + return HookCallback{.name = "toggle_play_pause", .fn = [&](lv_indev_data_t* d) { + events::Audio().Dispatch(audio::TogglePlayPause{}); + }}; } } // namespace actions diff --git a/src/tangara/input/input_hook_actions.hpp b/src/tangara/input/input_hook_actions.hpp index bca82af6..2db0b6e7 100644 --- a/src/tangara/input/input_hook_actions.hpp +++ b/src/tangara/input/input_hook_actions.hpp @@ -7,6 +7,7 @@ #pragma once #include "input/input_hook.hpp" +#include "audio/track_queue.hpp" namespace input { namespace actions { @@ -21,10 +22,13 @@ auto scrollToBottom() -> HookCallback; auto goBack() -> HookCallback; +auto togglePlayPause() -> HookCallback; + +auto nextTrack(audio::TrackQueue& queue) -> HookCallback; +auto prevTrack(audio::TrackQueue& queue) -> HookCallback; + auto volumeUp() -> HookCallback; auto volumeDown() -> HookCallback; -auto allActions() -> std::vector; - } // namespace actions } // namespace input diff --git a/src/tangara/input/input_media_buttons.cpp b/src/tangara/input/input_media_buttons.cpp new file mode 100644 index 00000000..94d7dd12 --- /dev/null +++ b/src/tangara/input/input_media_buttons.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2025 ailurux + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "input/input_media_buttons.hpp" +#include "drivers/gpios.hpp" +#include "events/event_queue.hpp" +#include "input/input_hook_actions.hpp" + +namespace input { + +MediaButtons::MediaButtons(drivers::IGpios& gpios, audio::TrackQueue& queue) + : gpios_(gpios), + up_("upper", actions::nextTrack(queue), {}, {}, actions::volumeUp()), + down_("lower", actions::prevTrack(queue), {}, {}, actions::volumeDown()), + locked_(), + both_buttons_pressed_(false) {} + +auto MediaButtons::read(lv_indev_data_t* data, std::vector& events) -> void { + bool up = !gpios_.Get(drivers::IGpios::Pin::kKeyUp); + bool down = !gpios_.Get(drivers::IGpios::Pin::kKeyDown); + + if ((up && down)) { + up_.cancel(); + down_.cancel(); + both_buttons_pressed_ = true; + } else if (!up && !down) { + if (both_buttons_pressed_) { + std::invoke(actions::togglePlayPause().fn, data); + both_buttons_pressed_ = false; + return; + } + } + + if (both_buttons_pressed_) { + return; + } + + up_.update(up, data); + down_.update(down, data); +} + +auto MediaButtons::name() -> std::string { + return "buttons"; +} + +auto MediaButtons::triggers() + -> std::vector> { + return {up_, down_}; +} + +auto MediaButtons::onLock() -> void { + locked_ = true; +} + +auto MediaButtons::onUnlock() -> void { + locked_ = false; +} + +} // namespace input diff --git a/src/tangara/input/input_media_buttons.hpp b/src/tangara/input/input_media_buttons.hpp new file mode 100644 index 00000000..02ee90d3 --- /dev/null +++ b/src/tangara/input/input_media_buttons.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2025 ailurux + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#include "indev/lv_indev.h" + +#include "drivers/gpios.hpp" +#include "drivers/haptics.hpp" +#include "drivers/touchwheel.hpp" +#include "input/input_device.hpp" +#include "input/input_hook.hpp" + +namespace input { + +class MediaButtons : public IInputDevice { + public: + MediaButtons(drivers::IGpios&, audio::TrackQueue& queue); + + auto read(lv_indev_data_t* data, std::vector& events) -> void override; + + auto name() -> std::string override; + auto triggers() -> std::vector> override; + + auto onLock() -> void override; + auto onUnlock() -> void override; + + private: + drivers::IGpios& gpios_; + + TriggerHooks up_; + TriggerHooks down_; + + bool locked_; + bool both_buttons_pressed_; +}; + +} // namespace input diff --git a/src/tangara/input/input_nav_buttons.cpp b/src/tangara/input/input_nav_buttons.cpp index ede6b714..b1279b44 100644 --- a/src/tangara/input/input_nav_buttons.cpp +++ b/src/tangara/input/input_nav_buttons.cpp @@ -42,7 +42,7 @@ auto NavButtons::triggers() return {up_, down_}; } -auto NavButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { +auto NavButtons::onLock() -> void { locked_ = true; } diff --git a/src/tangara/input/input_nav_buttons.hpp b/src/tangara/input/input_nav_buttons.hpp index c28cce91..03b337d3 100644 --- a/src/tangara/input/input_nav_buttons.hpp +++ b/src/tangara/input/input_nav_buttons.hpp @@ -28,7 +28,7 @@ class NavButtons : public IInputDevice { auto name() -> std::string override; auto triggers() -> std::vector> override; - auto onLock(drivers::NvsStorage::LockedInputModes) -> void override; + auto onLock() -> void override; auto onUnlock() -> void override; private: diff --git a/src/tangara/input/input_touch_dpad.cpp b/src/tangara/input/input_touch_dpad.cpp index c866f37c..b6c61237 100644 --- a/src/tangara/input/input_touch_dpad.cpp +++ b/src/tangara/input/input_touch_dpad.cpp @@ -65,7 +65,7 @@ auto TouchDPad::triggers() return {centre_, up_, right_, down_, left_}; } -auto TouchDPad::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { +auto TouchDPad::onLock() -> void { wheel_.LowPowerMode(true); locked_ = true; } diff --git a/src/tangara/input/input_touch_dpad.hpp b/src/tangara/input/input_touch_dpad.hpp index 82de26bf..bf772d73 100644 --- a/src/tangara/input/input_touch_dpad.hpp +++ b/src/tangara/input/input_touch_dpad.hpp @@ -27,7 +27,7 @@ class TouchDPad : public IInputDevice { auto name() -> std::string override; auto triggers() -> std::vector> override; - auto onLock(drivers::NvsStorage::LockedInputModes) -> void override; + auto onLock() -> void override; auto onUnlock() -> void override; private: diff --git a/src/tangara/input/input_touch_wheel.cpp b/src/tangara/input/input_touch_wheel.cpp index 29153396..ae192e31 100644 --- a/src/tangara/input/input_touch_wheel.cpp +++ b/src/tangara/input/input_touch_wheel.cpp @@ -130,7 +130,7 @@ auto TouchWheel::triggers() return {centre_, up_, right_, down_, left_}; } -auto TouchWheel::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { +auto TouchWheel::onLock() -> void { wheel_.LowPowerMode(true); locked_ = true; } diff --git a/src/tangara/input/input_touch_wheel.hpp b/src/tangara/input/input_touch_wheel.hpp index b2583e85..3f2ed49e 100644 --- a/src/tangara/input/input_touch_wheel.hpp +++ b/src/tangara/input/input_touch_wheel.hpp @@ -30,7 +30,7 @@ class TouchWheel : public IInputDevice { auto name() -> std::string override; auto triggers() -> std::vector> override; - auto onLock(drivers::NvsStorage::LockedInputModes) -> void override; + auto onLock() -> void override; auto onUnlock() -> void override; auto sensitivity() -> lua::Property&; diff --git a/src/tangara/input/input_volume_buttons.cpp b/src/tangara/input/input_volume_buttons.cpp index ffeea18f..d6c50e2f 100644 --- a/src/tangara/input/input_volume_buttons.cpp +++ b/src/tangara/input/input_volume_buttons.cpp @@ -21,9 +21,7 @@ auto VolumeButtons::read(lv_indev_data_t* data, std::vector& events) bool up = !gpios_.Get(drivers::IGpios::Pin::kKeyUp); bool down = !gpios_.Get(drivers::IGpios::Pin::kKeyDown); - bool input_disabled = locked_.has_value() && (locked_ != drivers::NvsStorage::LockedInputModes::kVolumeOnly); - - if ((up && down) || input_disabled) { + if ((up && down)) { up = false; down = false; } @@ -41,12 +39,12 @@ auto VolumeButtons::triggers() return {up_, down_}; } -auto VolumeButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void { - locked_ = mode; +auto VolumeButtons::onLock() -> void { + locked_ = true; } auto VolumeButtons::onUnlock() -> void { - locked_ = {}; + locked_ = false; } } // namespace input diff --git a/src/tangara/input/input_volume_buttons.hpp b/src/tangara/input/input_volume_buttons.hpp index 17f60d7e..8559d496 100644 --- a/src/tangara/input/input_volume_buttons.hpp +++ b/src/tangara/input/input_volume_buttons.hpp @@ -27,7 +27,7 @@ class VolumeButtons : public IInputDevice { auto name() -> std::string override; auto triggers() -> std::vector> override; - auto onLock(drivers::NvsStorage::LockedInputModes) -> void override; + auto onLock() -> void override; auto onUnlock() -> void override; private: @@ -36,8 +36,7 @@ class VolumeButtons : public IInputDevice { TriggerHooks up_; TriggerHooks down_; - // When locked, this contains the active mode - std::optional locked_; + bool locked_; }; } // namespace input diff --git a/src/tangara/input/lvgl_input_driver.cpp b/src/tangara/input/lvgl_input_driver.cpp index 03d5cbb7..b4bab365 100644 --- a/src/tangara/input/lvgl_input_driver.cpp +++ b/src/tangara/input/lvgl_input_driver.cpp @@ -55,63 +55,94 @@ static void focus_cb(lv_group_t* group) { namespace { -auto intToMode(int raw) -> std::optional { +auto intToWheelMode(int raw) + -> std::optional { switch (raw) { case 0: - return drivers::NvsStorage::InputModes::kButtonsOnly; + return drivers::NvsStorage::WheelInputModes::kDisabled; case 1: - return drivers::NvsStorage::InputModes::kButtonsWithWheel; + return drivers::NvsStorage::WheelInputModes::kDirectionalWheel; case 2: - return drivers::NvsStorage::InputModes::kDirectionalWheel; - case 3: - return drivers::NvsStorage::InputModes::kRotatingWheel; + return drivers::NvsStorage::WheelInputModes::kRotatingWheel; default: return {}; } } -auto intToLockedMode(int raw) -> std::optional { +auto intToButtonMode(int raw) + -> std::optional { switch (raw) { case 0: - return drivers::NvsStorage::LockedInputModes::kDisabled; + return drivers::NvsStorage::ButtonInputModes::kDisabled; case 1: - return drivers::NvsStorage::LockedInputModes::kVolumeOnly; + return drivers::NvsStorage::ButtonInputModes::kVolumeOnly; + case 2: + return drivers::NvsStorage::ButtonInputModes::kMediaControls; + case 3: + return drivers::NvsStorage::ButtonInputModes::kNavigation; default: return {}; } } -} // namespace {} +} // namespace LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory& factory) : nvs_(nvs), factory_(factory), - mode_(static_cast(nvs.PrimaryInput()), - [&](const lua::LuaValue& val) { - if (!std::holds_alternative(val)) { - return false; - } - auto mode = intToMode(std::get(val)); - if (!mode) { - return false; - } - nvs.PrimaryInput(*mode); - inputs_ = factory.createInputs(*mode); - return true; - }), + wheel_mode_( + static_cast(nvs.WheelInput()), + [&](const lua::LuaValue& val) { + if (!std::holds_alternative(val)) { + return false; + } + auto mode = intToWheelMode(std::get(val)); + if (!mode) { + return false; + } + // Only allow disabling wheel if side buttons are + // used for navigation + if (*mode == drivers::NvsStorage::WheelInputModes::kDisabled && + nvs.ButtonInput() != + drivers::NvsStorage::ButtonInputModes::kNavigation) { + return false; + } + nvs.WheelInput(*mode); + inputs_ = factory.createInputs(); + return true; + }), + button_mode_( + static_cast(nvs.ButtonInput()), + [&](const lua::LuaValue& val) { + if (!std::holds_alternative(val)) { + return false; + } + auto mode = intToButtonMode(std::get(val)); + if (!mode) { + return false; + } + // Ensure we don't remove the only navigation control + if (*mode != drivers::NvsStorage::ButtonInputModes::kNavigation && + nvs.WheelInput() == drivers::NvsStorage::WheelInputModes::kDisabled) { + return false; + } + nvs.ButtonInput(*mode); + inputs_ = factory.createInputs(); + return true; + }), locked_mode_(static_cast(nvs.LockedInput()), - [&](const lua::LuaValue& val) { - if (!std::holds_alternative(val)) { - return false; - } - auto mode = intToLockedMode(std::get(val)); - if (!mode) { - return false; - } - nvs.LockedInput(*mode); - return true; - }), + [&](const lua::LuaValue& val) { + if (!std::holds_alternative(val)) { + return false; + } + auto mode = intToButtonMode(std::get(val)); + if (!mode) { + return false; + } + nvs.LockedInput(*mode); + return true; + }), haptics_mode_(static_cast(nvs.HapticsMode()), [&](const lua::LuaValue& val) { if (!std::holds_alternative(val)) { @@ -121,7 +152,7 @@ LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs, nvs.HapticsMode(mode); return true; }), - inputs_(factory.createInputs(nvs.PrimaryInput())), + inputs_(factory.createInputs()), feedbacks_(factory.createFeedbacks()), is_locked_(false) { device_ = lv_indev_create(); @@ -172,14 +203,11 @@ auto LvglInputDriver::feedback(uint8_t event) -> void { auto LvglInputDriver::lock(bool l) -> void { is_locked_ = l; - auto locked_input_mode = nvs_.LockedInput(); - - for (auto&& device : inputs_) { - if (l) { - device->onLock(locked_input_mode); - } else { - device->onUnlock(); - } + // Todo:: Call onLock/onUnlock on inputs_? + if (l) { + inputs_ = factory_.createLockedInputs(); + } else { + inputs_ = factory_.createInputs(); } } diff --git a/src/tangara/input/lvgl_input_driver.hpp b/src/tangara/input/lvgl_input_driver.hpp index 629a0a78..ecb3a36d 100644 --- a/src/tangara/input/lvgl_input_driver.hpp +++ b/src/tangara/input/lvgl_input_driver.hpp @@ -35,7 +35,8 @@ class LvglInputDriver { public: LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory&); - auto mode() -> lua::Property& { return mode_; } + auto wheelMode() -> lua::Property& { return wheel_mode_; } + auto buttonMode() -> lua::Property& { return button_mode_; } auto lockedMode() -> lua::Property& { return locked_mode_; } auto hapticsMode() -> lua::Property& { return haptics_mode_; } @@ -50,7 +51,8 @@ class LvglInputDriver { drivers::NvsStorage& nvs_; DeviceFactory& factory_; - lua::Property mode_; + lua::Property wheel_mode_; + lua::Property button_mode_; lua::Property locked_mode_; lua::Property haptics_mode_; lv_indev_t* device_; @@ -73,8 +75,6 @@ class LvglInputDriver { std::tie(r.device_name, r.trigger_name, r.hook_name); } }; - - /* Userdata object for tracking the Lua mirror of a TriggerHooks object. */ class LuaTrigger { public: LuaTrigger(LvglInputDriver&, IInputDevice&, TriggerHooks&); @@ -96,13 +96,15 @@ class LvglInputDriver { std::string trigger_; std::map hooks_; }; - - /* A hook override implemented as a lua callback */ struct LuaOverride { lua_State* L; int ref; }; + /* Userdata object for tracking the Lua mirror of a TriggerHooks object. */ + + /* A hook override implemented as a lua callback */ + std::map overrides_; auto setOverride(lua_State* L, const OverrideSelector&) -> void; diff --git a/src/tangara/lua/lua_controls.cpp b/src/tangara/lua/lua_controls.cpp index bc2588ac..69053f43 100644 --- a/src/tangara/lua/lua_controls.cpp +++ b/src/tangara/lua/lua_controls.cpp @@ -23,37 +23,64 @@ namespace lua { [[maybe_unused]] static constexpr char kTag[] = "lua_controls"; -static auto controls_schemes(lua_State* L) -> int { +static auto wheel_schemes(lua_State* L) -> int { lua_newtable(L); - lua_pushliteral(L, "Buttons Only"); + lua_pushliteral(L, "Disabled"); lua_rawseti(L, -2, - static_cast(drivers::NvsStorage::InputModes::kButtonsOnly)); + static_cast(drivers::NvsStorage::WheelInputModes::kDisabled)); lua_pushliteral(L, "D-Pad"); + lua_rawseti(L, -2, + static_cast(drivers::NvsStorage::WheelInputModes::kDirectionalWheel)); + + lua_pushliteral(L, "Touchwheel"); + lua_rawseti( + L, -2, static_cast(drivers::NvsStorage::WheelInputModes::kRotatingWheel)); + + return 1; +} + +static auto button_schemes(lua_State* L) -> int { + lua_newtable(L); + + lua_pushliteral(L, "Disabled"); lua_rawseti( L, -2, - static_cast(drivers::NvsStorage::InputModes::kDirectionalWheel)); + static_cast(drivers::NvsStorage::ButtonInputModes::kDisabled)); - lua_pushliteral(L, "Touchwheel"); + lua_pushliteral(L, "Volume Only"); + lua_rawseti( + L, -2, + static_cast(drivers::NvsStorage::ButtonInputModes::kVolumeOnly)); + lua_pushliteral(L, "Media Controls"); lua_rawseti( - L, -2, static_cast(drivers::NvsStorage::InputModes::kRotatingWheel)); + L, -2, + static_cast(drivers::NvsStorage::ButtonInputModes::kMediaControls)); + lua_pushliteral(L, "Navigation"); + lua_rawseti( + L, -2, + static_cast(drivers::NvsStorage::ButtonInputModes::kNavigation)); return 1; } -static auto locked_controls_schemes(lua_State* L) -> int { +static auto locked_button_schemes(lua_State* L) -> int { lua_newtable(L); lua_pushliteral(L, "Disabled"); lua_rawseti( L, -2, - static_cast(drivers::NvsStorage::LockedInputModes::kDisabled)); + static_cast(drivers::NvsStorage::ButtonInputModes::kDisabled)); lua_pushliteral(L, "Volume Only"); lua_rawseti( L, -2, - static_cast(drivers::NvsStorage::LockedInputModes::kVolumeOnly)); + static_cast(drivers::NvsStorage::ButtonInputModes::kVolumeOnly)); + lua_pushliteral(L, "Media Controls"); + lua_rawseti( + L, -2, + static_cast(drivers::NvsStorage::ButtonInputModes::kMediaControls)); return 1; } @@ -77,8 +104,9 @@ static auto haptics_modes(lua_State* L) -> int { return 1; } -static const struct luaL_Reg kControlsFuncs[] = {{"schemes", controls_schemes}, - {"locked_schemes", locked_controls_schemes}, +static const struct luaL_Reg kControlsFuncs[] = {{"wheel_schemes", wheel_schemes}, + {"button_schemes", button_schemes}, + {"locked_schemes", locked_button_schemes}, {"haptics_modes", haptics_modes}, {NULL, NULL}}; diff --git a/src/tangara/ui/ui_fsm.cpp b/src/tangara/ui/ui_fsm.cpp index 020917e2..aaf35d13 100644 --- a/src/tangara/ui/ui_fsm.cpp +++ b/src/tangara/ui/ui_fsm.cpp @@ -680,7 +680,8 @@ void Lua::entry() { registry.AddPropertyModule( "controls", { - {"scheme", &sInput->mode()}, + {"wheel_scheme", &sInput->wheelMode()}, + {"button_scheme", &sInput->buttonMode()}, {"locked_scheme", &sInput->lockedMode()}, {"haptics_mode", &sInput->hapticsMode()}, {"lock_switch", &sLockSwitch},