parent
2e59325c22
commit
ed82063af5
@ -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
|
@ -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
|
|
Loading…
Reference in new issue