WIP decompose our giant LVGL driver into smaller classes
This commit is contained in:
@@ -5,8 +5,7 @@
|
||||
idf_component_register(
|
||||
SRCS "touchwheel.cpp" "i2s_dac.cpp" "gpios.cpp" "adc.cpp" "storage.cpp"
|
||||
"i2c.cpp" "bluetooth.cpp" "spi.cpp" "display.cpp" "display_init.cpp"
|
||||
"samd.cpp" "relative_wheel.cpp" "wm8523.cpp" "nvs.cpp" "haptics.cpp"
|
||||
"spiffs.cpp"
|
||||
"samd.cpp" "wm8523.cpp" "nvs.cpp" "haptics.cpp" "spiffs.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "spiffs"
|
||||
"bt" "tinyfsm" "util")
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "result.hpp"
|
||||
|
||||
#include "gpios.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
|
||||
namespace drivers {
|
||||
|
||||
class RelativeWheel {
|
||||
public:
|
||||
explicit RelativeWheel(TouchWheel& touch);
|
||||
|
||||
auto Update() -> void;
|
||||
auto SetEnabled(bool) -> void;
|
||||
|
||||
auto SetSensitivity(uint8_t) -> void;
|
||||
auto GetSensitivity() -> uint8_t;
|
||||
|
||||
auto is_clicking() const -> bool;
|
||||
auto ticks() const -> std::int_fast16_t;
|
||||
|
||||
// Not copyable or movable.
|
||||
RelativeWheel(const RelativeWheel&) = delete;
|
||||
RelativeWheel& operator=(const RelativeWheel&) = delete;
|
||||
|
||||
private:
|
||||
TouchWheel& touch_;
|
||||
|
||||
bool is_enabled_;
|
||||
uint8_t sensitivity_;
|
||||
uint8_t threshold_;
|
||||
|
||||
bool is_clicking_;
|
||||
bool was_clicking_;
|
||||
bool is_first_read_;
|
||||
std::int_fast16_t ticks_;
|
||||
uint8_t last_angle_;
|
||||
};
|
||||
|
||||
} // namespace drivers
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include "relative_wheel.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
namespace drivers {
|
||||
|
||||
RelativeWheel::RelativeWheel(TouchWheel& touch)
|
||||
: touch_(touch),
|
||||
is_enabled_(true),
|
||||
sensitivity_(128),
|
||||
threshold_(10),
|
||||
is_clicking_(false),
|
||||
was_clicking_(false),
|
||||
is_first_read_(true),
|
||||
ticks_(0),
|
||||
last_angle_(0) {}
|
||||
|
||||
auto RelativeWheel::Update() -> void {
|
||||
TouchWheelData d = touch_.GetTouchWheelData();
|
||||
|
||||
is_clicking_ = d.is_button_touched;
|
||||
|
||||
if (is_clicking_) {
|
||||
ticks_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!d.is_wheel_touched) {
|
||||
ticks_ = 0;
|
||||
is_first_read_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t new_angle = d.wheel_position;
|
||||
if (is_first_read_) {
|
||||
is_first_read_ = false;
|
||||
last_angle_ = new_angle;
|
||||
return;
|
||||
}
|
||||
|
||||
int delta = 128 - last_angle_;
|
||||
uint8_t rotated_angle = new_angle + delta;
|
||||
if (rotated_angle < 128 - threshold_) {
|
||||
ticks_ = 1;
|
||||
last_angle_ = new_angle;
|
||||
} else if (rotated_angle > 128 + threshold_) {
|
||||
ticks_ = -1;
|
||||
last_angle_ = new_angle;
|
||||
} else {
|
||||
ticks_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto RelativeWheel::SetEnabled(bool en) -> void {
|
||||
is_enabled_ = en;
|
||||
}
|
||||
|
||||
auto RelativeWheel::SetSensitivity(uint8_t val) -> void {
|
||||
sensitivity_ = val;
|
||||
int tmax = 35;
|
||||
int tmin = 5;
|
||||
threshold_ = (((255. - sensitivity_)/255.)*(tmax - tmin) + tmin);
|
||||
}
|
||||
|
||||
auto RelativeWheel::GetSensitivity() -> uint8_t {
|
||||
return sensitivity_;
|
||||
}
|
||||
|
||||
auto RelativeWheel::is_clicking() const -> bool {
|
||||
if (!is_enabled_) {
|
||||
return false;
|
||||
}
|
||||
return is_clicking_;
|
||||
}
|
||||
|
||||
auto RelativeWheel::ticks() const -> std::int_fast16_t {
|
||||
if (!is_enabled_) {
|
||||
return 0;
|
||||
}
|
||||
return ticks_;
|
||||
}
|
||||
|
||||
} // namespace drivers
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright 2023 jacqueline <me@jacqueline.id.au>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
idf_component_register(
|
||||
SRCS "input_touch_wheel.cpp" "input_touch_dpad.cpp"
|
||||
"input_volume_buttons.cpp" "lvgl_input_driver.cpp" "feedback_haptics.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "drivers" "lvgl" "events" "system_fsm")
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include "feedback_haptics.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "lvgl/lvgl.h"
|
||||
|
||||
#include "core/lv_event.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "haptics.hpp"
|
||||
|
||||
namespace input {
|
||||
|
||||
using Effect = drivers::Haptics::Effect;
|
||||
|
||||
Haptics::Haptics(drivers::Haptics& haptics_) : haptics_(haptics_) {}
|
||||
|
||||
auto Haptics::feedback(uint8_t event_type) -> void {
|
||||
switch (event_type) {
|
||||
case LV_EVENT_FOCUSED:
|
||||
haptics_.PlayWaveformEffect(Effect::kMediumClick1_100Pct);
|
||||
break;
|
||||
case LV_EVENT_CLICKED:
|
||||
haptics_.PlayWaveformEffect(Effect::kSharpClick_100Pct);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace input {
|
||||
|
||||
/*
|
||||
* Interface for providing non-visual feedback to the user as a result of LVGL
|
||||
* events. 'Feedback Devices' are able to observe all events that are generated
|
||||
* by LVGL as a result of Input Devices.
|
||||
*
|
||||
* Implementations of this interface are a mix of hardware features (e.g. a
|
||||
* haptic motor buzzing when your selection changes) and firmware features
|
||||
* (e.g. playing audio feedback that describes the selected element).
|
||||
*/
|
||||
class IFeedbackDevice {
|
||||
public:
|
||||
virtual ~IFeedbackDevice() {}
|
||||
|
||||
virtual auto feedback(uint8_t event_type) -> void = 0;
|
||||
|
||||
// TODO: Add configuration; likely the same shape of interface that
|
||||
// IInputDevice uses.
|
||||
};
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "feedback_device.hpp"
|
||||
#include "haptics.hpp"
|
||||
|
||||
namespace input {
|
||||
|
||||
class Haptics : public IFeedbackDevice {
|
||||
public:
|
||||
Haptics(drivers::Haptics& haptics_);
|
||||
|
||||
auto feedback(uint8_t event_type) -> void override;
|
||||
|
||||
private:
|
||||
drivers::Haptics& haptics_;
|
||||
};
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "hal/lv_hal_indev.h"
|
||||
|
||||
namespace input {
|
||||
|
||||
/*
|
||||
* Interface for all device input methods. Each 'Input Device' is polled by
|
||||
* LVGL at regular intervals, and can effect the device either via LVGL's input
|
||||
* device driver API, or by emitting events for other parts of the system to
|
||||
* react to (e.g. issuing a play/pause event, or altering the volume).
|
||||
*/
|
||||
class IInputDevice {
|
||||
public:
|
||||
virtual ~IInputDevice() {}
|
||||
|
||||
virtual auto read(lv_indev_data_t* data) -> void = 0;
|
||||
|
||||
// TODO: Add hooks and configuration (or are hooks just one kind of
|
||||
// configuration?)
|
||||
};
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "hal/lv_hal_indev.h"
|
||||
|
||||
#include "haptics.hpp"
|
||||
#include "input_device.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
|
||||
namespace input {
|
||||
|
||||
class TouchDPad : public IInputDevice {
|
||||
public:
|
||||
TouchDPad(drivers::TouchWheel&);
|
||||
|
||||
auto read(lv_indev_data_t* data) -> void override;
|
||||
|
||||
private:
|
||||
drivers::TouchWheel& wheel_;
|
||||
};
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "hal/lv_hal_indev.h"
|
||||
|
||||
#include "haptics.hpp"
|
||||
#include "input_device.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
|
||||
namespace input {
|
||||
|
||||
class TouchWheel : public IInputDevice {
|
||||
public:
|
||||
TouchWheel(drivers::TouchWheel&);
|
||||
|
||||
auto read(lv_indev_data_t* data) -> void override;
|
||||
|
||||
private:
|
||||
auto calculate_ticks(const drivers::TouchWheelData& data) -> int8_t;
|
||||
|
||||
drivers::TouchWheel& wheel_;
|
||||
|
||||
bool is_scrolling_;
|
||||
uint8_t sensitivity_;
|
||||
uint8_t threshold_;
|
||||
bool is_first_read_;
|
||||
uint8_t last_angle_;
|
||||
};
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "gpios.hpp"
|
||||
#include "hal/lv_hal_indev.h"
|
||||
|
||||
#include "haptics.hpp"
|
||||
#include "input_device.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
|
||||
namespace input {
|
||||
|
||||
class VolumeButtons : public IInputDevice {
|
||||
public:
|
||||
VolumeButtons(drivers::IGpios&);
|
||||
|
||||
auto read(lv_indev_data_t* data) -> void override;
|
||||
|
||||
private:
|
||||
drivers::IGpios& gpios_;
|
||||
};
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include "core/lv_group.h"
|
||||
#include "feedback_device.hpp"
|
||||
#include "gpios.hpp"
|
||||
#include "hal/lv_hal_indev.h"
|
||||
|
||||
#include "input_device.hpp"
|
||||
#include "nvs.hpp"
|
||||
#include "service_locator.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
|
||||
namespace input {
|
||||
|
||||
/*
|
||||
* Implementation of an LVGL input device. This class composes multiple
|
||||
* IInputDevice and IFeedbackDevice instances together into a single LVGL
|
||||
* device.
|
||||
*/
|
||||
class LvglInputDriver {
|
||||
public:
|
||||
LvglInputDriver(std::shared_ptr<system_fsm::ServiceLocator>);
|
||||
|
||||
auto read(lv_indev_data_t* data) -> void;
|
||||
auto feedback(uint8_t) -> void;
|
||||
|
||||
auto registration() -> lv_indev_t* { return registration_; }
|
||||
auto lock(bool l) -> void { is_locked_ = l; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<system_fsm::ServiceLocator> services_;
|
||||
|
||||
lv_indev_drv_t driver_;
|
||||
lv_indev_t* registration_;
|
||||
|
||||
std::vector<std::unique_ptr<IInputDevice>> inputs_;
|
||||
std::vector<std::unique_ptr<IFeedbackDevice>> feedbacks_;
|
||||
|
||||
bool is_locked_;
|
||||
};
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include "input_touch_dpad.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "hal/lv_hal_indev.h"
|
||||
|
||||
#include "haptics.hpp"
|
||||
#include "input_device.hpp"
|
||||
#include "input_touch_dpad.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
|
||||
namespace input {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
TouchDPad::TouchDPad(drivers::TouchWheel& wheel) : wheel_(wheel) {}
|
||||
|
||||
auto TouchDPad::read(lv_indev_data_t* data) -> void {
|
||||
wheel_.Update();
|
||||
|
||||
// TODO: reimplement
|
||||
}
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include "input_touch_wheel.hpp"
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "hal/lv_hal_indev.h"
|
||||
|
||||
#include "haptics.hpp"
|
||||
#include "input_device.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
|
||||
namespace input {
|
||||
|
||||
TouchWheel::TouchWheel(drivers::TouchWheel& wheel)
|
||||
: wheel_(wheel),
|
||||
is_scrolling_(false),
|
||||
sensitivity_(128),
|
||||
threshold_(10),
|
||||
is_first_read_(true),
|
||||
last_angle_(0) {}
|
||||
|
||||
auto TouchWheel::read(lv_indev_data_t* data) -> void {
|
||||
wheel_.Update();
|
||||
auto wheel_data = wheel_.GetTouchWheelData();
|
||||
int8_t ticks = calculate_ticks(wheel_data);
|
||||
|
||||
if (!wheel_data.is_wheel_touched) {
|
||||
// User has released the wheel.
|
||||
is_scrolling_ = false;
|
||||
data->enc_diff = 0;
|
||||
} else if (ticks != 0) {
|
||||
// User is touching the wheel, and has just passed the sensitivity
|
||||
// threshold for a scroll tick.
|
||||
is_scrolling_ = true;
|
||||
data->enc_diff = ticks;
|
||||
} else {
|
||||
// User is touching the wheel, but hasn't moved.
|
||||
data->enc_diff = 0;
|
||||
}
|
||||
|
||||
if (!is_scrolling_ && wheel_data.is_button_touched) {
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
} else {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
}
|
||||
}
|
||||
|
||||
auto TouchWheel::calculate_ticks(const drivers::TouchWheelData& data)
|
||||
-> int8_t {
|
||||
if (!data.is_wheel_touched) {
|
||||
is_first_read_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t new_angle = data.wheel_position;
|
||||
if (is_first_read_) {
|
||||
is_first_read_ = false;
|
||||
last_angle_ = new_angle;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int delta = 128 - last_angle_;
|
||||
uint8_t rotated_angle = new_angle + delta;
|
||||
if (rotated_angle < 128 - threshold_) {
|
||||
last_angle_ = new_angle;
|
||||
return 1;
|
||||
} else if (rotated_angle > 128 + threshold_) {
|
||||
last_angle_ = new_angle;
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include "input_volume_buttons.hpp"
|
||||
#include "gpios.hpp"
|
||||
|
||||
namespace input {
|
||||
|
||||
VolumeButtons::VolumeButtons(drivers::IGpios& gpios) : gpios_(gpios) {}
|
||||
|
||||
auto VolumeButtons::read(lv_indev_data_t* data) -> void {
|
||||
bool vol_up = gpios_.Get(drivers::IGpios::Pin::kKeyUp);
|
||||
if (!vol_up) {
|
||||
ESP_LOGI("volume", "vol up");
|
||||
}
|
||||
|
||||
bool vol_down = gpios_.Get(drivers::IGpios::Pin::kKeyDown);
|
||||
if (!vol_down) {
|
||||
ESP_LOGI("volume", "vol down");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace input
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include "lvgl_input_driver.hpp"
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "feedback_haptics.hpp"
|
||||
#include "input_touch_wheel.hpp"
|
||||
#include "input_volume_buttons.hpp"
|
||||
#include "lvgl.h"
|
||||
#include "service_locator.hpp"
|
||||
|
||||
[[maybe_unused]] static constexpr char kTag[] = "input";
|
||||
|
||||
namespace input {
|
||||
|
||||
static void read_cb(lv_indev_drv_t* drv, lv_indev_data_t* data) {
|
||||
LvglInputDriver* instance =
|
||||
reinterpret_cast<LvglInputDriver*>(drv->user_data);
|
||||
instance->read(data);
|
||||
}
|
||||
|
||||
static void feedback_cb(lv_indev_drv_t* drv, uint8_t event) {
|
||||
LvglInputDriver* instance =
|
||||
reinterpret_cast<LvglInputDriver*>(drv->user_data);
|
||||
instance->feedback(event);
|
||||
}
|
||||
|
||||
LvglInputDriver::LvglInputDriver(
|
||||
std::shared_ptr<system_fsm::ServiceLocator> services)
|
||||
: services_(services),
|
||||
driver_(),
|
||||
registration_(nullptr),
|
||||
inputs_(),
|
||||
feedbacks_(),
|
||||
is_locked_(false) {
|
||||
lv_indev_drv_init(&driver_);
|
||||
driver_.type = LV_INDEV_TYPE_ENCODER;
|
||||
driver_.read_cb = read_cb;
|
||||
driver_.feedback_cb = feedback_cb;
|
||||
driver_.user_data = this;
|
||||
|
||||
registration_ = lv_indev_drv_register(&driver_);
|
||||
|
||||
// TODO: Make these devices configurable. I'm thinking each device gets an id
|
||||
// and then we have:
|
||||
// - a factory to create instance given an id
|
||||
// - add/remove device methods on LvglInputDriver that operate on ids
|
||||
// - the user's enabled devices (+ their configuration) stored in NVS.
|
||||
auto touchwheel = services_->touchwheel();
|
||||
if (touchwheel) {
|
||||
inputs_.push_back(std::make_unique<TouchWheel>(**touchwheel));
|
||||
}
|
||||
inputs_.push_back(std::make_unique<VolumeButtons>(services_->gpios()));
|
||||
feedbacks_.push_back(std::make_unique<Haptics>(services_->haptics()));
|
||||
}
|
||||
|
||||
auto LvglInputDriver::read(lv_indev_data_t* data) -> void {
|
||||
// TODO: we should pass lock state on to the individual devices, since they
|
||||
// may wish to either ignore the lock state, or power down until unlock.
|
||||
if (is_locked_) {
|
||||
return;
|
||||
}
|
||||
for (auto&& device : inputs_) {
|
||||
device->read(data);
|
||||
}
|
||||
}
|
||||
|
||||
auto LvglInputDriver::feedback(uint8_t event) -> void {
|
||||
if (is_locked_) {
|
||||
return;
|
||||
}
|
||||
for (auto&& device : feedbacks_) {
|
||||
device->feedback(event);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace input
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "display.hpp"
|
||||
#include "gpios.hpp"
|
||||
#include "nvs.hpp"
|
||||
#include "relative_wheel.hpp"
|
||||
#include "samd.hpp"
|
||||
#include "service_locator.hpp"
|
||||
#include "storage.hpp"
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "event_queue.hpp"
|
||||
#include "gpios.hpp"
|
||||
#include "relative_wheel.hpp"
|
||||
#include "service_locator.hpp"
|
||||
#include "system_events.hpp"
|
||||
#include "tag_parser.hpp"
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
idf_component_register(
|
||||
SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "encoder_input.cpp"
|
||||
"themes.cpp" "screen.cpp" "modal.cpp" "screen_lua.cpp"
|
||||
"splash.c" "font_fusion_12.c" "font_fusion_10.c"
|
||||
SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "themes.cpp"
|
||||
"screen.cpp" "modal.cpp" "screen_lua.cpp" "splash.c" "font_fusion_12.c"
|
||||
"font_fusion_10.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "lua" "luavgl" "esp_app_format")
|
||||
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "lua" "luavgl" "esp_app_format" "input")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
|
||||
|
||||
@@ -1,358 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include "encoder_input.hpp"
|
||||
|
||||
#include <sys/_stdint.h>
|
||||
#include <memory>
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
#include "audio_events.hpp"
|
||||
#include "core/lv_event.h"
|
||||
#include "core/lv_group.h"
|
||||
#include "esp_timer.h"
|
||||
#include "event_queue.hpp"
|
||||
#include "gpios.hpp"
|
||||
#include "hal/lv_hal_indev.h"
|
||||
#include "nvs.hpp"
|
||||
#include "relative_wheel.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
#include "ui_events.hpp"
|
||||
|
||||
[[maybe_unused]] static constexpr char kTag[] = "input";
|
||||
|
||||
constexpr int kDPadAngleThreshold = 10;
|
||||
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) {
|
||||
EncoderInput* instance = reinterpret_cast<EncoderInput*>(drv->user_data);
|
||||
instance->Read(data);
|
||||
}
|
||||
|
||||
EncoderInput::EncoderInput(drivers::IGpios& gpios, drivers::TouchWheel& wheel)
|
||||
: gpios_(gpios),
|
||||
raw_wheel_(wheel),
|
||||
relative_wheel_(std::make_unique<drivers::RelativeWheel>(wheel)),
|
||||
scroller_(std::make_unique<Scroller>()),
|
||||
mode_(drivers::NvsStorage::InputModes::kRotatingWheel),
|
||||
is_locked_(false),
|
||||
scroll_sensitivity_(10),
|
||||
is_scrolling_wheel_(false) {
|
||||
lv_indev_drv_init(&driver_);
|
||||
driver_.type = LV_INDEV_TYPE_ENCODER;
|
||||
driver_.read_cb = encoder_read;
|
||||
driver_.user_data = this;
|
||||
|
||||
registration_ = lv_indev_drv_register(&driver_);
|
||||
}
|
||||
|
||||
auto EncoderInput::Read(lv_indev_data_t* data) -> void {
|
||||
if (is_locked_) {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_obj_t* active_object = nullptr;
|
||||
if (registration_ && registration_->group) {
|
||||
active_object = lv_group_get_focused(registration_->group);
|
||||
}
|
||||
|
||||
raw_wheel_.Update();
|
||||
relative_wheel_->Update();
|
||||
// GPIO (for volume buttons) 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.
|
||||
UpdateKeyState(Keys::kVolumeUp, now_ms,
|
||||
!gpios_.Get(drivers::IGpios::Pin::kKeyUp));
|
||||
UpdateKeyState(Keys::kVolumeDown, now_ms,
|
||||
!gpios_.Get(drivers::IGpios::Pin::kKeyDown));
|
||||
|
||||
drivers::TouchWheelData wheel_data = raw_wheel_.GetTouchWheelData();
|
||||
UpdateKeyState(Keys::kTouchWheel, now_ms, wheel_data.is_wheel_touched);
|
||||
UpdateKeyState(Keys::kTouchWheelCenter, now_ms, wheel_data.is_button_touched);
|
||||
|
||||
UpdateKeyState(
|
||||
Keys::kDirectionalUp, now_ms,
|
||||
wheel_data.is_wheel_touched &&
|
||||
IsAngleWithin(wheel_data.wheel_position, 0, kDPadAngleThreshold));
|
||||
UpdateKeyState(
|
||||
Keys::kDirectionalLeft, now_ms,
|
||||
wheel_data.is_wheel_touched &&
|
||||
IsAngleWithin(wheel_data.wheel_position, 63, kDPadAngleThreshold));
|
||||
UpdateKeyState(
|
||||
Keys::kDirectionalDown, now_ms,
|
||||
wheel_data.is_wheel_touched &&
|
||||
IsAngleWithin(wheel_data.wheel_position, 127, kDPadAngleThreshold));
|
||||
UpdateKeyState(
|
||||
Keys::kDirectionalRight, now_ms,
|
||||
wheel_data.is_wheel_touched &&
|
||||
IsAngleWithin(wheel_data.wheel_position, 189, kDPadAngleThreshold));
|
||||
|
||||
// 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;
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
|
||||
trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kLongPress, now_ms);
|
||||
switch (trigger) {
|
||||
case Trigger::kNone:
|
||||
break;
|
||||
case Trigger::kClick:
|
||||
data->enc_diff = -1;
|
||||
break;
|
||||
case Trigger::kLongPress:
|
||||
events::Ui().Dispatch(internal::BackPressed{});
|
||||
break;
|
||||
}
|
||||
|
||||
trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kLongPress, now_ms);
|
||||
switch (trigger) {
|
||||
case Trigger::kNone:
|
||||
break;
|
||||
case Trigger::kClick:
|
||||
data->enc_diff = 1;
|
||||
break;
|
||||
case Trigger::kLongPress:
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
break;
|
||||
}
|
||||
} else if (mode_ == drivers::NvsStorage::InputModes::kDirectionalWheel) {
|
||||
Trigger trigger;
|
||||
trigger = TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, now_ms);
|
||||
data->state = trigger == Trigger::kClick ? LV_INDEV_STATE_PRESSED
|
||||
: LV_INDEV_STATE_RELEASED;
|
||||
|
||||
trigger = TriggerKey(Keys::kDirectionalUp, KeyStyle::kRepeat, now_ms);
|
||||
if (trigger == Trigger::kClick) {
|
||||
data->enc_diff = scroller_->AddInput(now_ms, -1);
|
||||
}
|
||||
|
||||
trigger = TriggerKey(Keys::kDirectionalDown, KeyStyle::kRepeat, now_ms);
|
||||
if (trigger == Trigger::kClick) {
|
||||
data->enc_diff = scroller_->AddInput(now_ms, 1);
|
||||
}
|
||||
|
||||
trigger = TriggerKey(Keys::kDirectionalLeft, KeyStyle::kRepeat, now_ms);
|
||||
if (trigger == Trigger::kClick) {
|
||||
events::Ui().Dispatch(internal::BackPressed{});
|
||||
}
|
||||
|
||||
trigger = TriggerKey(Keys::kDirectionalRight, KeyStyle::kRepeat, now_ms);
|
||||
if (trigger == Trigger::kClick) {
|
||||
// TODO: ???
|
||||
}
|
||||
|
||||
// Cancel scrolling if the touchpad is released.
|
||||
if (!touch_time_ms_.contains(Keys::kDirectionalUp) &&
|
||||
!touch_time_ms_.contains(Keys::kDirectionalDown)) {
|
||||
data->enc_diff = scroller_->AddInput(now_ms, 0);
|
||||
}
|
||||
|
||||
trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kRepeat, now_ms);
|
||||
switch (trigger) {
|
||||
case Trigger::kNone:
|
||||
break;
|
||||
case Trigger::kClick:
|
||||
events::Audio().Dispatch(audio::StepUpVolume{});
|
||||
break;
|
||||
case Trigger::kLongPress:
|
||||
break;
|
||||
}
|
||||
|
||||
trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kRepeat, now_ms);
|
||||
switch (trigger) {
|
||||
case Trigger::kNone:
|
||||
break;
|
||||
case Trigger::kClick:
|
||||
events::Audio().Dispatch(audio::StepDownVolume{});
|
||||
break;
|
||||
case Trigger::kLongPress:
|
||||
break;
|
||||
}
|
||||
} else if (mode_ == drivers::NvsStorage::InputModes::kRotatingWheel) {
|
||||
if (!raw_wheel_.GetTouchWheelData().is_wheel_touched) {
|
||||
// User has released the wheel.
|
||||
is_scrolling_wheel_ = false;
|
||||
data->enc_diff = scroller_->AddInput(now_ms, 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());
|
||||
} else {
|
||||
// User is touching the wheel, but hasn't moved.
|
||||
data->enc_diff = 0;
|
||||
}
|
||||
|
||||
Trigger trigger =
|
||||
TriggerKey(Keys::kTouchWheelCenter, KeyStyle::kLongPress, now_ms);
|
||||
switch (trigger) {
|
||||
case Trigger::kNone:
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
break;
|
||||
case Trigger::kClick:
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
break;
|
||||
case Trigger::kLongPress:
|
||||
if (active_object) {
|
||||
lv_event_send(active_object, LV_EVENT_LONG_PRESSED, NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
trigger = TriggerKey(Keys::kVolumeUp, KeyStyle::kRepeat, now_ms);
|
||||
switch (trigger) {
|
||||
case Trigger::kNone:
|
||||
break;
|
||||
case Trigger::kClick:
|
||||
events::Audio().Dispatch(audio::StepUpVolume{});
|
||||
break;
|
||||
case Trigger::kLongPress:
|
||||
break;
|
||||
}
|
||||
|
||||
trigger = TriggerKey(Keys::kVolumeDown, KeyStyle::kRepeat, now_ms);
|
||||
switch (trigger) {
|
||||
case Trigger::kNone:
|
||||
break;
|
||||
case Trigger::kClick:
|
||||
events::Audio().Dispatch(audio::StepDownVolume{});
|
||||
break;
|
||||
case Trigger::kLongPress:
|
||||
break;
|
||||
}
|
||||
|
||||
trigger = TriggerKey(Keys::kDirectionalLeft, KeyStyle::kLongPress, now_ms);
|
||||
switch (trigger) {
|
||||
case Trigger::kNone:
|
||||
break;
|
||||
case Trigger::kClick:
|
||||
break;
|
||||
case Trigger::kLongPress:
|
||||
events::Ui().Dispatch(internal::BackPressed{});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto EncoderInput::scroll_sensitivity(uint8_t val) -> void {
|
||||
scroll_sensitivity_ = val;
|
||||
relative_wheel_->SetSensitivity(scroll_sensitivity_);
|
||||
}
|
||||
|
||||
auto EncoderInput::UpdateKeyState(Keys key, uint64_t ms, bool clicked) -> void {
|
||||
if (clicked) {
|
||||
if (!touch_time_ms_.contains(key)) {
|
||||
// Key was just clicked.
|
||||
touch_time_ms_[key] = ms;
|
||||
just_released_.erase(key);
|
||||
fired_.erase(key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Key is not clicked.
|
||||
if (touch_time_ms_.contains(key)) {
|
||||
// Key was just released.
|
||||
just_released_.insert(key);
|
||||
touch_time_ms_.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
auto EncoderInput::TriggerKey(Keys key, KeyStyle s, uint64_t ms) -> Trigger {
|
||||
if (s == KeyStyle::kRepeat) {
|
||||
bool may_repeat = fired_.contains(key) && touch_time_ms_.contains(key) &&
|
||||
ms - touch_time_ms_[key] >= kRepeatDelayMs;
|
||||
|
||||
// Repeatable keys trigger on press.
|
||||
if (touch_time_ms_.contains(key) && (!fired_.contains(key) || may_repeat)) {
|
||||
fired_.insert(key);
|
||||
return Trigger::kClick;
|
||||
} else {
|
||||
return Trigger::kNone;
|
||||
}
|
||||
} else if (s == KeyStyle::kLongPress) {
|
||||
// Long press keys trigger on release, or after holding for a delay.
|
||||
if (just_released_.contains(key)) {
|
||||
just_released_.erase(key);
|
||||
if (!fired_.contains(key)) {
|
||||
fired_.insert(key);
|
||||
return Trigger::kClick;
|
||||
}
|
||||
}
|
||||
if (touch_time_ms_.contains(key) &&
|
||||
ms - touch_time_ms_[key] >= kLongPressDelayMs &&
|
||||
!fired_.contains(key)) {
|
||||
fired_.insert(key);
|
||||
return Trigger::kLongPress;
|
||||
}
|
||||
}
|
||||
|
||||
return Trigger::kNone;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#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.
|
||||
*
|
||||
* As far as LVGL is concerned, this class represents an ordinary rotary
|
||||
* encoder, supporting only left and right ticks, and clicking.
|
||||
*/
|
||||
class EncoderInput {
|
||||
public:
|
||||
EncoderInput(drivers::IGpios& gpios, drivers::TouchWheel& wheel);
|
||||
|
||||
auto Read(lv_indev_data_t* data) -> void;
|
||||
auto registration() -> lv_indev_t* { return registration_; }
|
||||
|
||||
auto mode(drivers::NvsStorage::InputModes mode) { mode_ = mode; }
|
||||
auto scroll_sensitivity(uint8_t val) -> void; // Value between 0-255, used to scale the threshold
|
||||
auto lock(bool l) -> void { is_locked_ = l; }
|
||||
|
||||
private:
|
||||
lv_indev_drv_t driver_;
|
||||
lv_indev_t* registration_;
|
||||
|
||||
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_;
|
||||
uint8_t scroll_sensitivity_;
|
||||
|
||||
// Every kind of distinct input that we could map to an action.
|
||||
enum class Keys {
|
||||
kVolumeUp,
|
||||
kVolumeDown,
|
||||
kTouchWheel,
|
||||
kTouchWheelCenter,
|
||||
kDirectionalUp,
|
||||
kDirectionalRight,
|
||||
kDirectionalDown,
|
||||
kDirectionalLeft,
|
||||
};
|
||||
|
||||
// Map from a Key, to the time that it was first touched in ms. If the key is
|
||||
// currently released, where will be no entry.
|
||||
std::unordered_map<Keys, uint64_t> touch_time_ms_;
|
||||
// Set of keys that were released during the current update.
|
||||
std::set<Keys> just_released_;
|
||||
// Set of keys that have had an event fired for them since being pressed.
|
||||
std::set<Keys> fired_;
|
||||
|
||||
bool is_scrolling_wheel_;
|
||||
|
||||
enum class Trigger {
|
||||
kNone,
|
||||
// Regular short-click. Triggered on release for long-pressable keys,
|
||||
// triggered on the initial press for repeatable keys.
|
||||
kClick,
|
||||
kLongPress,
|
||||
};
|
||||
|
||||
enum class KeyStyle {
|
||||
kRepeat,
|
||||
kLongPress,
|
||||
};
|
||||
|
||||
auto UpdateKeyState(Keys key, uint64_t ms, bool clicked) -> void;
|
||||
auto TriggerKey(Keys key, KeyStyle t, uint64_t ms) -> Trigger;
|
||||
};
|
||||
|
||||
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
|
||||
@@ -15,8 +15,7 @@
|
||||
#include "freertos/timers.h"
|
||||
|
||||
#include "display.hpp"
|
||||
#include "encoder_input.hpp"
|
||||
#include "relative_wheel.hpp"
|
||||
#include "lvgl_input_driver.hpp"
|
||||
#include "screen.hpp"
|
||||
#include "themes.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
@@ -28,14 +27,14 @@ class UiTask {
|
||||
static auto Start() -> UiTask*;
|
||||
~UiTask();
|
||||
|
||||
auto input(std::shared_ptr<EncoderInput> input) -> void;
|
||||
auto input(std::shared_ptr<input::LvglInputDriver> input) -> void;
|
||||
|
||||
private:
|
||||
UiTask();
|
||||
|
||||
auto Main() -> void;
|
||||
|
||||
std::shared_ptr<EncoderInput> input_;
|
||||
std::shared_ptr<input::LvglInputDriver> input_;
|
||||
std::shared_ptr<Screen> current_screen_;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,14 +14,13 @@
|
||||
#include "battery.hpp"
|
||||
#include "db_events.hpp"
|
||||
#include "display.hpp"
|
||||
#include "encoder_input.hpp"
|
||||
#include "gpios.hpp"
|
||||
#include "lua_thread.hpp"
|
||||
#include "lvgl_input_driver.hpp"
|
||||
#include "lvgl_task.hpp"
|
||||
#include "modal.hpp"
|
||||
#include "nvs.hpp"
|
||||
#include "property.hpp"
|
||||
#include "relative_wheel.hpp"
|
||||
#include "screen.hpp"
|
||||
#include "service_locator.hpp"
|
||||
#include "storage.hpp"
|
||||
@@ -92,7 +91,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
|
||||
static std::unique_ptr<UiTask> sTask;
|
||||
static std::shared_ptr<system_fsm::ServiceLocator> sServices;
|
||||
static std::unique_ptr<drivers::Display> sDisplay;
|
||||
static std::shared_ptr<EncoderInput> sInput;
|
||||
static std::shared_ptr<input::LvglInputDriver> sInput;
|
||||
|
||||
static std::stack<std::shared_ptr<Screen>> sScreens;
|
||||
static std::shared_ptr<Screen> sCurrentScreen;
|
||||
|
||||
+2
-13
@@ -33,11 +33,11 @@
|
||||
#include "lua.h"
|
||||
#include "lv_api_map.h"
|
||||
#include "lvgl/lvgl.h"
|
||||
#include "lvgl_input_driver.hpp"
|
||||
#include "misc/lv_color.h"
|
||||
#include "misc/lv_style.h"
|
||||
#include "misc/lv_timer.h"
|
||||
#include "modal.hpp"
|
||||
#include "relative_wheel.hpp"
|
||||
#include "tasks.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
#include "ui_fsm.hpp"
|
||||
@@ -50,8 +50,6 @@ namespace ui {
|
||||
|
||||
[[maybe_unused]] static const char* kTag = "ui_task";
|
||||
|
||||
static auto group_focus_cb(lv_group_t *group) -> void;
|
||||
|
||||
UiTask::UiTask() {}
|
||||
|
||||
UiTask::~UiTask() {
|
||||
@@ -78,7 +76,6 @@ auto UiTask::Main() -> void {
|
||||
if (input_ && current_screen_->group() != current_group) {
|
||||
current_group = current_screen_->group();
|
||||
lv_indev_set_group(input_->registration(), current_group);
|
||||
lv_group_set_focus_cb(current_group, &group_focus_cb);
|
||||
}
|
||||
|
||||
TickType_t delay = lv_timer_handler();
|
||||
@@ -86,10 +83,9 @@ auto UiTask::Main() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto UiTask::input(std::shared_ptr<EncoderInput> input) -> void {
|
||||
auto UiTask::input(std::shared_ptr<input::LvglInputDriver> input) -> void {
|
||||
assert(current_screen_);
|
||||
input_ = input;
|
||||
lv_indev_set_group(input_->registration(), current_screen_->group());
|
||||
}
|
||||
|
||||
auto UiTask::Start() -> UiTask* {
|
||||
@@ -98,11 +94,4 @@ auto UiTask::Start() -> UiTask* {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static auto group_focus_cb(lv_group_t *group) -> void {
|
||||
// TODO(robin): we probably want to vary this, configure this, etc
|
||||
events::System().Dispatch(system_fsm::HapticTrigger{
|
||||
.effect = drivers::Haptics::Effect::kMediumClick1_100Pct,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
+56
-64
@@ -30,19 +30,18 @@
|
||||
#include "lauxlib.h"
|
||||
#include "lua_thread.hpp"
|
||||
#include "luavgl.h"
|
||||
#include "lvgl_input_driver.hpp"
|
||||
#include "memory_resource.hpp"
|
||||
#include "misc/lv_gc.h"
|
||||
|
||||
#include "audio_events.hpp"
|
||||
#include "display.hpp"
|
||||
#include "encoder_input.hpp"
|
||||
#include "event_queue.hpp"
|
||||
#include "gpios.hpp"
|
||||
#include "lua_registry.hpp"
|
||||
#include "lvgl_task.hpp"
|
||||
#include "nvs.hpp"
|
||||
#include "property.hpp"
|
||||
#include "relative_wheel.hpp"
|
||||
#include "samd.hpp"
|
||||
#include "screen.hpp"
|
||||
#include "screen_lua.hpp"
|
||||
@@ -63,7 +62,7 @@ namespace ui {
|
||||
std::unique_ptr<UiTask> UiState::sTask;
|
||||
std::shared_ptr<system_fsm::ServiceLocator> UiState::sServices;
|
||||
std::unique_ptr<drivers::Display> UiState::sDisplay;
|
||||
std::shared_ptr<EncoderInput> UiState::sInput;
|
||||
std::shared_ptr<input::LvglInputDriver> UiState::sInput;
|
||||
|
||||
std::stack<std::shared_ptr<Screen>> UiState::sScreens;
|
||||
std::shared_ptr<Screen> UiState::sCurrentScreen;
|
||||
@@ -234,51 +233,58 @@ lua::Property UiState::sDisplayBrightness{
|
||||
return true;
|
||||
}};
|
||||
|
||||
lua::Property UiState::sControlsScheme{
|
||||
0, [](const lua::LuaValue& val) {
|
||||
if (!std::holds_alternative<int>(val)) {
|
||||
return false;
|
||||
}
|
||||
drivers::NvsStorage::InputModes mode;
|
||||
switch (std::get<int>(val)) {
|
||||
case 0:
|
||||
mode = drivers::NvsStorage::InputModes::kButtonsOnly;
|
||||
break;
|
||||
case 1:
|
||||
mode = drivers::NvsStorage::InputModes::kButtonsWithWheel;
|
||||
break;
|
||||
case 2:
|
||||
mode = drivers::NvsStorage::InputModes::kDirectionalWheel;
|
||||
break;
|
||||
case 3:
|
||||
mode = drivers::NvsStorage::InputModes::kRotatingWheel;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
sServices->nvs().PrimaryInput(mode);
|
||||
sInput->mode(mode);
|
||||
return true;
|
||||
}};
|
||||
lua::Property UiState::sControlsScheme{0, [](const lua::LuaValue& val) {
|
||||
/*
|
||||
if (!std::holds_alternative<int>(val))
|
||||
{ return false;
|
||||
}
|
||||
drivers::NvsStorage::InputModes mode;
|
||||
switch (std::get<int>(val)) {
|
||||
case 0:
|
||||
mode =
|
||||
drivers::NvsStorage::InputModes::kButtonsOnly;
|
||||
break;
|
||||
case 1:
|
||||
mode =
|
||||
drivers::NvsStorage::InputModes::kButtonsWithWheel;
|
||||
break;
|
||||
case 2:
|
||||
mode =
|
||||
drivers::NvsStorage::InputModes::kDirectionalWheel;
|
||||
break;
|
||||
case 3:
|
||||
mode =
|
||||
drivers::NvsStorage::InputModes::kRotatingWheel;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
sServices->nvs().PrimaryInput(mode);
|
||||
sInput->mode(mode);
|
||||
*/
|
||||
return true;
|
||||
}};
|
||||
|
||||
lua::Property UiState::sScrollSensitivity{
|
||||
0, [](const lua::LuaValue& val) {
|
||||
std::optional<int> sensitivity = 0;
|
||||
std::visit(
|
||||
[&](auto&& v) {
|
||||
using T = std::decay_t<decltype(v)>;
|
||||
if constexpr (std::is_same_v<T, int>) {
|
||||
sensitivity = v;
|
||||
}
|
||||
},
|
||||
val);
|
||||
if (!sensitivity) {
|
||||
return false;
|
||||
}
|
||||
sInput->scroll_sensitivity(*sensitivity);
|
||||
sServices->nvs().ScrollSensitivity(*sensitivity);
|
||||
return true;
|
||||
}};
|
||||
lua::Property UiState::sScrollSensitivity{0, [](const lua::LuaValue& val) {
|
||||
/*
|
||||
std::optional<int> sensitivity = 0;
|
||||
std::visit(
|
||||
[&](auto&& v) {
|
||||
using T =
|
||||
std::decay_t<decltype(v)>; if
|
||||
constexpr (std::is_same_v<T, int>) {
|
||||
sensitivity = v;
|
||||
}
|
||||
},
|
||||
val);
|
||||
if (!sensitivity) {
|
||||
return false;
|
||||
}
|
||||
sInput->scroll_sensitivity(*sensitivity);
|
||||
sServices->nvs().ScrollSensitivity(*sensitivity);
|
||||
*/
|
||||
return true;
|
||||
}};
|
||||
|
||||
lua::Property UiState::sLockSwitch{false};
|
||||
|
||||
@@ -372,7 +378,7 @@ void UiState::react(const internal::ControlSchemeChanged&) {
|
||||
if (!sInput) {
|
||||
return;
|
||||
}
|
||||
sInput->mode(sServices->nvs().PrimaryInput());
|
||||
// sInput->mode(sServices->nvs().PrimaryInput());
|
||||
}
|
||||
|
||||
void UiState::react(const database::event::UpdateStarted&) {
|
||||
@@ -478,22 +484,8 @@ void Splash::react(const system_fsm::BootComplete& ev) {
|
||||
sDisplayBrightness.Update(brightness);
|
||||
sDisplay->SetBrightness(brightness);
|
||||
|
||||
auto touchwheel = sServices->touchwheel();
|
||||
if (touchwheel) {
|
||||
sInput = std::make_shared<EncoderInput>(sServices->gpios(), **touchwheel);
|
||||
|
||||
auto mode = sServices->nvs().PrimaryInput();
|
||||
sInput->mode(mode);
|
||||
sControlsScheme.Update(static_cast<int>(mode));
|
||||
|
||||
auto sensitivity = sServices->nvs().ScrollSensitivity();
|
||||
sInput->scroll_sensitivity(sensitivity);
|
||||
sScrollSensitivity.Update(static_cast<int>(sensitivity));
|
||||
|
||||
sTask->input(sInput);
|
||||
} else {
|
||||
ESP_LOGE(kTag, "no input devices initialised!");
|
||||
}
|
||||
sInput = std::make_shared<input::LvglInputDriver>(sServices);
|
||||
sTask->input(sInput);
|
||||
}
|
||||
|
||||
void Splash::react(const system_fsm::StorageMounted&) {
|
||||
|
||||
Reference in New Issue
Block a user