mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-17 23:55:04 +00:00
Trackball rewrite (#600)
- Implement generic trackball settings - Refactor T-Deck trackball driver into generic driver at `Drivers/gpio-trackball-module` - Automatically bind/unbind trackball devices with LVGL - Automatically load settings for trackball devices on boot
This commit is contained in:
parent
de6fc3b346
commit
c729e8340f
@ -2,5 +2,5 @@ file(GLOB_RECURSE SOURCE_FILES source/*.c*)
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${SOURCE_FILES}
|
||||
REQUIRES Tactility driver lilygo-module
|
||||
REQUIRES TactilityKernel lilygo-module
|
||||
)
|
||||
|
||||
@ -4,6 +4,7 @@ dependencies:
|
||||
- Drivers/st7789-module
|
||||
- Drivers/gt911-module
|
||||
- Drivers/lilygo-module
|
||||
- Drivers/gpio-trackball-module
|
||||
- Drivers/es7210-module
|
||||
- Drivers/dummy-i2s-amp-module
|
||||
- Drivers/audio-stream-module
|
||||
|
||||
@ -19,12 +19,12 @@
|
||||
#include <bindings/st7789.h>
|
||||
#include <bindings/es7210.h>
|
||||
#include <bindings/dummy_i2s_amp.h>
|
||||
#include <bindings/gpio_trackball.h>
|
||||
|
||||
#include <gps_generic/bindings.h>
|
||||
|
||||
#include <lilygo/bindings/tdeck_keyboard.h>
|
||||
#include <lilygo/bindings/tdeck_keyboard_backlight.h>
|
||||
#include <lilygo/bindings/tdeck_trackball.h>
|
||||
|
||||
// Reference: https://wiki.lilygo.cc/products/t-deck-series/t-deck-plus/
|
||||
/ {
|
||||
@ -61,7 +61,7 @@
|
||||
};
|
||||
|
||||
trackball {
|
||||
compatible = "lilygo,tdeck-trackball";
|
||||
compatible = "tactility,gpio-trackball";
|
||||
pin-right = <&gpio0 2 GPIO_FLAG_NONE>;
|
||||
pin-up = <&gpio0 3 GPIO_FLAG_NONE>;
|
||||
pin-left = <&gpio0 1 GPIO_FLAG_NONE>;
|
||||
|
||||
@ -1,57 +1,25 @@
|
||||
#include <tactility/delay.h>
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/module.h>
|
||||
#include <tactility/system_event.h>
|
||||
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/settings/TrackballSettings.h>
|
||||
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <lilygo/drivers/trackball.h>
|
||||
#include <lilygo/drivers/tdeck_power_on.h>
|
||||
|
||||
constexpr auto* TAG = "tdeck-plus";
|
||||
|
||||
extern "C" {
|
||||
|
||||
void init_trackball() {
|
||||
auto tbSettings = tt::settings::trackball::loadOrGetDefault();
|
||||
lvgl_lock();
|
||||
if (trackball::init() != nullptr) {
|
||||
trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer
|
||||
? trackball::Mode::Pointer
|
||||
: trackball::Mode::Encoder);
|
||||
trackball::setEncoderSensitivity(tbSettings.encoderSensitivity);
|
||||
trackball::setPointerSensitivity(tbSettings.pointerSensitivity);
|
||||
trackball::setEnabled(tbSettings.trackballEnabled);
|
||||
}
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
static void on_boot_completed(struct SystemEvent* /*event*/, void* /*context*/) {
|
||||
init_trackball();
|
||||
}
|
||||
|
||||
static error_t start() {
|
||||
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
|
||||
LOG_I(TAG, "Power on start");
|
||||
|
||||
if (!tdeck_power_on()) {
|
||||
LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED);
|
||||
LOG_E(TAG, "Power on failed");
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
// Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw.
|
||||
delay_millis(100);
|
||||
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed, nullptr);
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
@ -2,5 +2,5 @@ file(GLOB_RECURSE SOURCE_FILES source/*.c*)
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${SOURCE_FILES}
|
||||
REQUIRES Tactility driver lilygo-module
|
||||
REQUIRES TactilityKernel lilygo-module
|
||||
)
|
||||
|
||||
@ -3,6 +3,7 @@ dependencies:
|
||||
- Drivers/st7789-module
|
||||
- Drivers/gt911-module
|
||||
- Drivers/lilygo-module
|
||||
- Drivers/gpio-trackball-module
|
||||
- Drivers/es7210-module
|
||||
- Drivers/dummy-i2s-amp-module
|
||||
- Drivers/audio-stream-module
|
||||
|
||||
@ -19,10 +19,10 @@
|
||||
#include <bindings/st7789.h>
|
||||
#include <bindings/es7210.h>
|
||||
#include <bindings/dummy_i2s_amp.h>
|
||||
#include <bindings/gpio_trackball.h>
|
||||
|
||||
#include <lilygo/bindings/tdeck_keyboard.h>
|
||||
#include <lilygo/bindings/tdeck_keyboard_backlight.h>
|
||||
#include <lilygo/bindings/tdeck_trackball.h>
|
||||
|
||||
// Reference: https://wiki.lilygo.cc/products/t-deck-series/t-deck/
|
||||
/ {
|
||||
@ -59,7 +59,7 @@
|
||||
};
|
||||
|
||||
trackball {
|
||||
compatible = "lilygo,tdeck-trackball";
|
||||
compatible = "tactility,gpio-trackball";
|
||||
pin-right = <&gpio0 2 GPIO_FLAG_NONE>;
|
||||
pin-up = <&gpio0 3 GPIO_FLAG_NONE>;
|
||||
pin-left = <&gpio0 1 GPIO_FLAG_NONE>;
|
||||
|
||||
@ -1,55 +1,20 @@
|
||||
#include <tactility/module.h>
|
||||
#include <tactility/delay.h>
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <tactility/system_event.h>
|
||||
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/kernel/Kernel.h>
|
||||
#include <Tactility/settings/TrackballSettings.h>
|
||||
|
||||
#include <lilygo/drivers/trackball.h>
|
||||
#include <lilygo/drivers/tdeck_power_on.h>
|
||||
|
||||
constexpr auto* TAG = "tdeck";
|
||||
|
||||
extern "C" {
|
||||
|
||||
static void on_boot_completed(struct SystemEvent* /*event*/, void* /*context*/) {
|
||||
auto tbSettings = tt::settings::trackball::loadOrGetDefault();
|
||||
lvgl_lock();
|
||||
if (trackball::init() != nullptr) {
|
||||
trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer
|
||||
? trackball::Mode::Pointer
|
||||
: trackball::Mode::Encoder);
|
||||
trackball::setEncoderSensitivity(tbSettings.encoderSensitivity);
|
||||
trackball::setPointerSensitivity(tbSettings.pointerSensitivity);
|
||||
trackball::setEnabled(tbSettings.trackballEnabled);
|
||||
}
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
void subscribe_events() {
|
||||
// The kernel trackball device is already started by kernel_init(); this just registers it as an
|
||||
// LVGL input device and applies persisted settings, both of which require LVGL to be up first.
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed, nullptr);
|
||||
}
|
||||
|
||||
static error_t start() {
|
||||
LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
|
||||
LOG_I(TAG, "Power on start");
|
||||
if (!tdeck_power_on()) {
|
||||
LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED);
|
||||
LOG_E(TAG, "Power on failed");
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
// Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw.
|
||||
delay_millis(100);
|
||||
|
||||
subscribe_events();
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
11
Drivers/gpio-trackball-module/CMakeLists.txt
Normal file
11
Drivers/gpio-trackball-module/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
||||
|
||||
tactility_add_module(gpio-trackball-module
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS include/
|
||||
REQUIRES TactilityKernel
|
||||
)
|
||||
195
Drivers/gpio-trackball-module/LICENSE-Apache-2.0.md
Normal file
195
Drivers/gpio-trackball-module/LICENSE-Apache-2.0.md
Normal file
@ -0,0 +1,195 @@
|
||||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### Terms and Conditions for use, reproduction, and distribution
|
||||
|
||||
#### 1. Definitions
|
||||
|
||||
“License” shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
“Licensor” shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
“Legal Entity” shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, “control” means **(i)** the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or **(iii)** beneficial ownership of such entity.
|
||||
|
||||
“You” (or “Your”) shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
“Source” form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
“Object” form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
“Work” shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
“Derivative Works” shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
“Contribution” shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
“submitted” means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as “Not a Contribution.”
|
||||
|
||||
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
#### 2. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
#### 3. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
#### 4. Redistribution
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
* **(b)** You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
#### 5. Submission of Contributions
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
#### 6. Trademarks
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
#### 7. Disclaimer of Warranty
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
#### 8. Limitation of Liability
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
#### 9. Accepting Warranty or Additional Liability
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
_END OF TERMS AND CONDITIONS_
|
||||
|
||||
### APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets `[]` replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same “printed page” as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
27
Drivers/gpio-trackball-module/README.md
Normal file
27
Drivers/gpio-trackball-module/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# GPIO Trackball
|
||||
|
||||
Kernel driver for a 5-way GPIO trackball (4 direction pins + a click button), exposing a
|
||||
`TRACKBALL_TYPE` device (`tactility/drivers/trackball.h`).
|
||||
|
||||
Devicetree binding: `tactility,gpio-trackball` (see `bindings/tactility,gpio-trackball.yaml`).
|
||||
|
||||
Each direction pin is wired to a falling-edge interrupt that accumulates a signed delta;
|
||||
the click pin is active-low and read on any edge. `read_delta()` drains and resets the
|
||||
accumulated delta on each call.
|
||||
|
||||
## Example
|
||||
|
||||
```dts
|
||||
trackball {
|
||||
compatible = "tactility,gpio-trackball";
|
||||
pin-right = <&gpio0 1 GPIO_FLAG_NONE>;
|
||||
pin-up = <&gpio0 2 GPIO_FLAG_NONE>;
|
||||
pin-left = <&gpio0 3 GPIO_FLAG_NONE>;
|
||||
pin-down = <&gpio0 4 GPIO_FLAG_NONE>;
|
||||
pin-click = <&gpio0 5 GPIO_FLAG_NONE>;
|
||||
};
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[Apache License Version 2.0](LICENSE-Apache-2.0.md)
|
||||
@ -1,6 +1,6 @@
|
||||
description: LilyGO T-Deck 5-way GPIO trackball (4 directions + click button)
|
||||
description: 5-way GPIO trackball (4 directions + click button)
|
||||
|
||||
compatible: "lilygo,tdeck-trackball"
|
||||
compatible: "tactility,gpio-trackball"
|
||||
|
||||
properties:
|
||||
pin-right:
|
||||
3
Drivers/gpio-trackball-module/devicetree.yaml
Normal file
3
Drivers/gpio-trackball-module/devicetree.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <drivers/gpio_trackball.h>
|
||||
|
||||
DEFINE_DEVICETREE(gpio_trackball, struct GpioTrackballConfig)
|
||||
@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <tactility/drivers/gpio.h>
|
||||
#include <tactility/error.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct Device;
|
||||
|
||||
struct GpioTrackballConfig {
|
||||
struct GpioPinSpec pin_right;
|
||||
struct GpioPinSpec pin_up;
|
||||
struct GpioPinSpec pin_left;
|
||||
struct GpioPinSpec pin_down;
|
||||
struct GpioPinSpec pin_click;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/module.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern struct Module gpio_trackball_module;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -1,20 +1,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lilygo/drivers/tdeck_trackball.h>
|
||||
#include <drivers/gpio_trackball.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/gpio_controller.h>
|
||||
#include <tactility/drivers/gpio_descriptor.h>
|
||||
#include <tactility/drivers/trackball.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <new>
|
||||
|
||||
#define TAG "tdeck_trackball"
|
||||
#define GET_CONFIG(device) (static_cast<const TdeckTrackballConfig*>((device)->config))
|
||||
#define GET_INTERNAL(device) (static_cast<TdeckTrackballInternal*>(device_get_driver_data(device)))
|
||||
constexpr auto* TAG = "gpio_trackball";
|
||||
|
||||
struct TdeckTrackballInternal {
|
||||
#define GET_CONFIG(device) (static_cast<const GpioTrackballConfig*>((device)->config))
|
||||
#define GET_INTERNAL(device) (static_cast<GpioTrackballInternal*>(device_get_driver_data(device)))
|
||||
|
||||
struct GpioTrackballInternal {
|
||||
GpioDescriptor* pin_right = nullptr;
|
||||
GpioDescriptor* pin_up = nullptr;
|
||||
GpioDescriptor* pin_left = nullptr;
|
||||
@ -29,23 +31,23 @@ struct TdeckTrackballInternal {
|
||||
// region ISR callbacks
|
||||
|
||||
static void on_right(void* arg) {
|
||||
static_cast<TdeckTrackballInternal*>(arg)->dx.fetch_add(1, std::memory_order_relaxed);
|
||||
static_cast<GpioTrackballInternal*>(arg)->dx.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static void on_left(void* arg) {
|
||||
static_cast<TdeckTrackballInternal*>(arg)->dx.fetch_sub(1, std::memory_order_relaxed);
|
||||
static_cast<GpioTrackballInternal*>(arg)->dx.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static void on_down(void* arg) {
|
||||
static_cast<TdeckTrackballInternal*>(arg)->dy.fetch_add(1, std::memory_order_relaxed);
|
||||
static_cast<GpioTrackballInternal*>(arg)->dy.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static void on_up(void* arg) {
|
||||
static_cast<TdeckTrackballInternal*>(arg)->dy.fetch_sub(1, std::memory_order_relaxed);
|
||||
static_cast<GpioTrackballInternal*>(arg)->dy.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static void on_click(void* arg) {
|
||||
auto* internal = static_cast<TdeckTrackballInternal*>(arg);
|
||||
auto* internal = static_cast<GpioTrackballInternal*>(arg);
|
||||
bool high = true;
|
||||
gpio_descriptor_get_level(internal->pin_click, &high);
|
||||
// Active low: pressed when level is low
|
||||
@ -90,7 +92,7 @@ static void release_pin(GpioDescriptor*& descriptor) {
|
||||
descriptor = nullptr;
|
||||
}
|
||||
|
||||
static void release_all_pins(TdeckTrackballInternal* internal) {
|
||||
static void release_all_pins(GpioTrackballInternal* internal) {
|
||||
release_pin(internal->pin_right);
|
||||
release_pin(internal->pin_up);
|
||||
release_pin(internal->pin_left);
|
||||
@ -114,19 +116,11 @@ static error_t get_button_pressed(Device* device, bool* out_pressed) {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t tdeck_trackball_read_delta(Device* device, int32_t* out_dx, int32_t* out_dy) {
|
||||
return read_delta(device, out_dx, out_dy);
|
||||
}
|
||||
|
||||
error_t tdeck_trackball_get_button_pressed(Device* device, bool* out_pressed) {
|
||||
return get_button_pressed(device, out_pressed);
|
||||
}
|
||||
|
||||
static error_t start(Device* device) {
|
||||
LOG_I(TAG, "start %s", device->name);
|
||||
auto* config = GET_CONFIG(device);
|
||||
|
||||
auto* internal = new(std::nothrow) TdeckTrackballInternal();
|
||||
auto* internal = new(std::nothrow) GpioTrackballInternal();
|
||||
if (internal == nullptr) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
@ -168,25 +162,21 @@ static error_t stop(Device* device) {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static constexpr TdeckTrackballApi TDECK_TRACKBALL_API = {
|
||||
static constexpr TrackballApi GPIO_TRACKBALL_API = {
|
||||
.read_delta = read_delta,
|
||||
.get_button_pressed = get_button_pressed,
|
||||
};
|
||||
|
||||
const struct DeviceType TDECK_TRACKBALL_TYPE {
|
||||
.name = "tdeck-trackball"
|
||||
};
|
||||
extern Module gpio_trackball_module;
|
||||
|
||||
extern Module lilygo_module;
|
||||
|
||||
Driver tdeck_trackball_driver = {
|
||||
.name = "tdeck_trackball",
|
||||
.compatible = (const char*[]) { "lilygo,tdeck-trackball", nullptr },
|
||||
Driver gpio_trackball_driver = {
|
||||
.name = "gpio_trackball",
|
||||
.compatible = (const char*[]) { "tactility,gpio-trackball", nullptr },
|
||||
.start_device = start,
|
||||
.stop_device = stop,
|
||||
.api = &TDECK_TRACKBALL_API,
|
||||
.device_type = &TDECK_TRACKBALL_TYPE,
|
||||
.owner = &lilygo_module,
|
||||
.api = &GPIO_TRACKBALL_API,
|
||||
.device_type = &TRACKBALL_TYPE,
|
||||
.owner = &gpio_trackball_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
19
Drivers/gpio-trackball-module/source/module.cpp
Normal file
19
Drivers/gpio-trackball-module/source/module.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern Driver gpio_trackball_driver;
|
||||
|
||||
static Driver* const gpio_trackball_drivers[] = {
|
||||
&gpio_trackball_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module gpio_trackball_module = {
|
||||
.name = "gpio-trackball",
|
||||
.drivers = gpio_trackball_drivers
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
@ -1,7 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <lilygo/drivers/tdeck_trackball.h>
|
||||
|
||||
DEFINE_DEVICETREE(tdeck_trackball, struct TdeckTrackballConfig)
|
||||
@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace trackball {
|
||||
|
||||
/**
|
||||
* @brief Trackball operating mode
|
||||
*/
|
||||
enum class Mode {
|
||||
Encoder, // Navigation via enc_diff (scroll wheel behavior)
|
||||
Pointer // Mouse cursor via point.x/y
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize trackball as an LVGL input device, backed by the kernel tdeck_trackball driver.
|
||||
* @return LVGL input device pointer, or nullptr if the kernel device isn't found/started
|
||||
*/
|
||||
lv_indev_t* init();
|
||||
|
||||
/**
|
||||
* @brief Deinitialize trackball
|
||||
*/
|
||||
void deinit();
|
||||
|
||||
/**
|
||||
* @brief Set encoder mode sensitivity
|
||||
* @param sensitivity Steps per trackball tick (1-10, default: 1)
|
||||
*/
|
||||
void setEncoderSensitivity(uint8_t sensitivity);
|
||||
|
||||
/**
|
||||
* @brief Set pointer mode sensitivity
|
||||
* @param sensitivity Pixels per trackball tick (1-10, default: 10)
|
||||
*/
|
||||
void setPointerSensitivity(uint8_t sensitivity);
|
||||
|
||||
/**
|
||||
* @brief Enable or disable trackball input processing
|
||||
* @param enabled Boolean value to enable or disable
|
||||
*/
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Set trackball operating mode
|
||||
* @param mode Encoder or Pointer mode
|
||||
*/
|
||||
void setMode(Mode mode);
|
||||
|
||||
/**
|
||||
* @brief Get current trackball operating mode
|
||||
* @return Current mode
|
||||
*/
|
||||
Mode getMode();
|
||||
|
||||
}
|
||||
@ -6,13 +6,11 @@ extern "C" {
|
||||
|
||||
extern Driver tdeck_keyboard_driver;
|
||||
extern Driver tdeck_keyboard_backlight_driver;
|
||||
extern Driver tdeck_trackball_driver;
|
||||
extern Driver tpager_encoder_driver;
|
||||
|
||||
static Driver* const lilygo_drivers[] = {
|
||||
&tdeck_keyboard_driver,
|
||||
&tdeck_keyboard_backlight_driver,
|
||||
&tdeck_trackball_driver,
|
||||
&tpager_encoder_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
@ -1,201 +0,0 @@
|
||||
#include <lilygo/drivers/trackball.h>
|
||||
#include <lilygo/drivers/tdeck_trackball.h>
|
||||
|
||||
#include <Tactility/Assets.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
|
||||
constexpr auto* TAG = "Trackball";
|
||||
|
||||
namespace trackball {
|
||||
|
||||
static lv_indev_t* g_indev = nullptr;
|
||||
static Device* g_device = nullptr;
|
||||
static bool g_enabled = true;
|
||||
static Mode g_mode = Mode::Encoder;
|
||||
static uint8_t g_encoderSensitivity = 1;
|
||||
static uint8_t g_pointerSensitivity = 10;
|
||||
|
||||
// Pointer mode cursor position (screen-relative)
|
||||
static int32_t g_cursorX = 160;
|
||||
static int32_t g_cursorY = 120;
|
||||
|
||||
static lv_obj_t* g_cursor = nullptr;
|
||||
|
||||
// Screen dimensions (T-Deck: 320x240)
|
||||
static constexpr int32_t SCREEN_WIDTH = 320;
|
||||
static constexpr int32_t SCREEN_HEIGHT = 240;
|
||||
static constexpr int32_t CURSOR_SIZE = 16;
|
||||
|
||||
static inline int32_t clamp(int32_t val, int32_t minVal, int32_t maxVal) {
|
||||
if (val < minVal) return minVal;
|
||||
if (val > maxVal) return maxVal;
|
||||
return val;
|
||||
}
|
||||
|
||||
// Note: must be called from the LVGL thread (main thread), same as the setters below.
|
||||
static void read_cb(lv_indev_t* /*indev*/, lv_indev_data_t* data) {
|
||||
// Always drain accumulated movement so it doesn't jump on re-enable, but discard it while disabled.
|
||||
int32_t dx = 0;
|
||||
int32_t dy = 0;
|
||||
tdeck_trackball_read_delta(g_device, &dx, &dy);
|
||||
if (!g_enabled) {
|
||||
dx = 0;
|
||||
dy = 0;
|
||||
}
|
||||
|
||||
if (g_mode == Mode::Encoder) {
|
||||
int32_t ticks = (dx + dy) * static_cast<int32_t>(g_encoderSensitivity);
|
||||
data->enc_diff = static_cast<int16_t>(clamp(ticks, INT16_MIN, INT16_MAX));
|
||||
if (ticks != 0) {
|
||||
lv_display_trigger_activity(nullptr);
|
||||
}
|
||||
} else {
|
||||
g_cursorX = clamp(g_cursorX + dx * static_cast<int32_t>(g_pointerSensitivity), 0, SCREEN_WIDTH - CURSOR_SIZE - 1);
|
||||
g_cursorY = clamp(g_cursorY + dy * static_cast<int32_t>(g_pointerSensitivity), 0, SCREEN_HEIGHT - CURSOR_SIZE - 1);
|
||||
data->point.x = static_cast<int16_t>(g_cursorX);
|
||||
data->point.y = static_cast<int16_t>(g_cursorY);
|
||||
}
|
||||
|
||||
bool pressed = false;
|
||||
if (g_enabled) {
|
||||
tdeck_trackball_get_button_pressed(g_device, &pressed);
|
||||
}
|
||||
data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
|
||||
if (pressed) {
|
||||
lv_display_trigger_activity(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
lv_indev_t* init() {
|
||||
if (g_indev != nullptr) {
|
||||
LOG_W(TAG, "Already initialized");
|
||||
return g_indev;
|
||||
}
|
||||
|
||||
if (device_get_first_active_by_type(&TDECK_TRACKBALL_TYPE, &g_device) != ERROR_NONE) {
|
||||
LOG_E(TAG, "tdeck_trackball kernel device not found or not started");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
g_cursorX = SCREEN_WIDTH / 2;
|
||||
g_cursorY = SCREEN_HEIGHT / 2;
|
||||
|
||||
g_indev = lv_indev_create();
|
||||
if (g_indev == nullptr) {
|
||||
LOG_E(TAG, "Failed to register LVGL input device");
|
||||
device_put(g_device);
|
||||
g_device = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
lv_indev_set_type(g_indev, LV_INDEV_TYPE_ENCODER);
|
||||
lv_indev_set_read_cb(g_indev, read_cb);
|
||||
LOG_I(TAG, "Initialized");
|
||||
|
||||
return g_indev;
|
||||
}
|
||||
|
||||
// Create cursor for pointer mode
|
||||
static void createCursor() {
|
||||
if (g_cursor != nullptr || g_indev == nullptr) return;
|
||||
|
||||
g_cursor = lv_image_create(lv_layer_sys());
|
||||
if (g_cursor != nullptr) {
|
||||
lv_obj_remove_flag(g_cursor, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_image_set_src(g_cursor, TT_ASSETS_UI_CURSOR);
|
||||
lv_indev_set_cursor(g_indev, g_cursor);
|
||||
LOG_D(TAG, "Cursor created");
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy cursor when switching back to encoder mode
|
||||
static void destroyCursor() {
|
||||
if (g_cursor == nullptr) return;
|
||||
|
||||
// Delete the cursor object - this automatically detaches it from the indev
|
||||
lv_obj_delete(g_cursor);
|
||||
g_cursor = nullptr;
|
||||
LOG_D(TAG, "Cursor destroyed");
|
||||
}
|
||||
|
||||
void deinit() {
|
||||
if (g_indev == nullptr) return;
|
||||
|
||||
destroyCursor();
|
||||
|
||||
lv_indev_delete(g_indev);
|
||||
g_indev = nullptr;
|
||||
|
||||
device_put(g_device);
|
||||
g_device = nullptr;
|
||||
|
||||
g_mode = Mode::Encoder;
|
||||
g_enabled = true;
|
||||
LOG_I(TAG, "Deinitialized");
|
||||
}
|
||||
|
||||
void setEncoderSensitivity(uint8_t sensitivity) {
|
||||
if (sensitivity > 0) {
|
||||
g_encoderSensitivity = sensitivity;
|
||||
LOG_D(TAG, "Encoder sensitivity set to %d", sensitivity);
|
||||
}
|
||||
}
|
||||
|
||||
void setPointerSensitivity(uint8_t sensitivity) {
|
||||
if (sensitivity > 0) {
|
||||
g_pointerSensitivity = sensitivity;
|
||||
LOG_D(TAG, "Pointer sensitivity set to %d", sensitivity);
|
||||
}
|
||||
}
|
||||
|
||||
void setEnabled(bool enabled) {
|
||||
g_enabled = enabled;
|
||||
|
||||
if (g_cursor != nullptr) {
|
||||
if (enabled) {
|
||||
lv_obj_clear_flag(g_cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(g_cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_I(TAG, "%s", enabled ? "Enabled" : "Disabled");
|
||||
}
|
||||
|
||||
void setMode(Mode mode) {
|
||||
if (g_indev == nullptr) {
|
||||
LOG_W(TAG, "Cannot set mode - not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_mode == mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_mode = mode;
|
||||
|
||||
if (mode == Mode::Pointer) {
|
||||
lv_indev_set_type(g_indev, LV_INDEV_TYPE_POINTER);
|
||||
createCursor();
|
||||
if (!g_enabled && g_cursor != nullptr) {
|
||||
lv_obj_add_flag(g_cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
g_cursorX = SCREEN_WIDTH / 2;
|
||||
g_cursorY = SCREEN_HEIGHT / 2;
|
||||
LOG_I(TAG, "Switched to Pointer mode");
|
||||
} else {
|
||||
destroyCursor();
|
||||
lv_indev_set_type(g_indev, LV_INDEV_TYPE_ENCODER);
|
||||
LOG_I(TAG, "Switched to Encoder mode");
|
||||
}
|
||||
}
|
||||
|
||||
Mode getMode() {
|
||||
return g_mode;
|
||||
}
|
||||
|
||||
}
|
||||
26
Modules/lvgl-module/include/lvgl/devices/device_context.h
Normal file
26
Modules/lvgl-module/include/lvgl/devices/device_context.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
|
||||
struct Device;
|
||||
|
||||
/**
|
||||
* Common driver-data wrapper attached to every LVGL display/indev created by this module: bundles
|
||||
* the kernel Device* with an optional device-type-specific context blob (LvglDisplayCtx,
|
||||
* LvglPointerCtx, LvglTrackballCtx, ...), so each device type doesn't have to store its own Device*
|
||||
* redundantly. Owns context: any resources referenced through it (buffers, cursor objects, etc.)
|
||||
* must be released by the caller before the wrapper is deleted, since context is trivially
|
||||
* destructible and only its own memory is freed here.
|
||||
*/
|
||||
struct LvglDeviceContext {
|
||||
struct Device* device = nullptr;
|
||||
void* context;
|
||||
|
||||
explicit LvglDeviceContext(void* context) : context(context) {}
|
||||
|
||||
~LvglDeviceContext() {
|
||||
if (context != nullptr) {
|
||||
::operator delete(context);
|
||||
}
|
||||
}
|
||||
};
|
||||
107
Modules/lvgl-module/include/lvgl/devices/trackball.h
Normal file
107
Modules/lvgl-module/include/lvgl/devices/trackball.h
Normal file
@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Trackball operating mode.
|
||||
*/
|
||||
enum LvglTrackballMode {
|
||||
/** Navigation via enc_diff (scroll wheel behavior). No cursor is shown. */
|
||||
LVGL_TRACKBALL_MODE_ENCODER,
|
||||
/** Mouse cursor via point.x/point.y. A cursor is shown if an image was set via
|
||||
* lvgl_trackball_set_cursor_image(). */
|
||||
LVGL_TRACKBALL_MODE_POINTER
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Runtime-adjustable trackball behavior. See lvgl_trackball_set_settings()/
|
||||
* lvgl_trackball_get_settings().
|
||||
*/
|
||||
struct LvglTrackballSettings {
|
||||
enum LvglTrackballMode mode;
|
||||
/** When false, movement and button presses are drained from the device and discarded:
|
||||
* the indev reports no movement and stays released. */
|
||||
bool enabled;
|
||||
/** Encoder mode: steps per raw trackball tick. Must be >= 1. */
|
||||
uint8_t encoder_sensitivity;
|
||||
/** Pointer mode: pixels per raw trackball tick. Must be >= 1. */
|
||||
uint8_t pointer_sensitivity;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Creates an lv_indev_t bound to the given TRACKBALL_TYPE device and registers a read
|
||||
* callback that polls the device through its TrackballApi.
|
||||
*
|
||||
* The indev starts in LVGL_TRACKBALL_MODE_ENCODER, enabled, with default sensitivities (see
|
||||
* lvgl_trackball_settings_get_default()). Call lvgl_trackball_set_settings() to change this,
|
||||
* e.g. to switch to pointer mode - each device added this way can independently run with or
|
||||
* without pointer support.
|
||||
*
|
||||
* @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) — call this from
|
||||
* LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly.
|
||||
*
|
||||
* @param[in] device a device of type TRACKBALL_TYPE
|
||||
* @param[in] display the display this indev should be associated with, or NULL to leave it unset
|
||||
* @param[out] out_indev the created indev, valid only when ERROR_NONE is returned
|
||||
* @retval ERROR_NONE on success
|
||||
* @retval ERROR_INVALID_ARGUMENT if device or out_indev is NULL, or device is not of type TRACKBALL_TYPE
|
||||
* @retval ERROR_OUT_OF_MEMORY if allocation failed
|
||||
*/
|
||||
error_t lvgl_trackball_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev);
|
||||
|
||||
/**
|
||||
* @brief Removes an indev previously created with lvgl_trackball_add(), including its cursor
|
||||
* object (if one was showing).
|
||||
* @warning Caller must hold the LVGL lock.
|
||||
*/
|
||||
void lvgl_trackball_remove(lv_indev_t* indev);
|
||||
|
||||
/**
|
||||
* @return the default (all fields explicitly set) LvglTrackballSettings: encoder mode, enabled,
|
||||
* encoder_sensitivity 1, pointer_sensitivity 10.
|
||||
*/
|
||||
struct LvglTrackballSettings lvgl_trackball_settings_get_default(void);
|
||||
|
||||
/**
|
||||
* @brief Applies settings to an indev previously created with lvgl_trackball_add(). Switching
|
||||
* `mode` shows/hides the cursor (pointer mode) and re-centers it on the indev's display.
|
||||
* @warning Caller must hold the LVGL lock.
|
||||
* @param[in] indev an indev previously created by lvgl_trackball_add()
|
||||
* @param[in] settings the settings to apply
|
||||
* @retval ERROR_NONE on success
|
||||
* @retval ERROR_INVALID_ARGUMENT if indev or settings is NULL, or either sensitivity is 0
|
||||
*/
|
||||
error_t lvgl_trackball_set_settings(lv_indev_t* indev, const struct LvglTrackballSettings* settings);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the settings currently active on indev.
|
||||
* @warning Caller must hold the LVGL lock.
|
||||
* @return true when indev is a trackball indev (out_settings is filled), false otherwise
|
||||
*/
|
||||
bool lvgl_trackball_get_settings(lv_indev_t* indev, struct LvglTrackballSettings* out_settings);
|
||||
|
||||
/**
|
||||
* @brief Sets the image shown as the mouse cursor while indev is in LVGL_TRACKBALL_MODE_POINTER.
|
||||
* lvgl-module has no bundled cursor asset (it doesn't depend on any particular asset layout), so
|
||||
* pointer mode has no visible cursor until a caller supplies one here; the indev still moves and
|
||||
* reports point.x/point.y either way. Pass NULL to remove the cursor.
|
||||
* @warning Caller must hold the LVGL lock.
|
||||
* @param[in] indev an indev previously created by lvgl_trackball_add()
|
||||
* @param[in] image_src an LVGL image source (as accepted by lv_image_set_src()), or NULL
|
||||
*/
|
||||
void lvgl_trackball_set_cursor_image(lv_indev_t* indev, const void* image_src);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -1,119 +0,0 @@
|
||||
#include <lvgl/devices/display.h>
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
#include <lvgl/devices/pointer.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#define TAG "lvgl"
|
||||
|
||||
void lvgl_devices_attach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_disp_t* lvgl_display = NULL;
|
||||
bool display_updates_slowly = false;
|
||||
struct Device* kernel_display_device = NULL;
|
||||
device_get_first_by_type(&DISPLAY_TYPE, &kernel_display_device);
|
||||
|
||||
// Placeholder drivers (boards not yet migrated to the kernel display driver) register with a
|
||||
// NULL api: they exist so the devicetree node resolves, but have nothing for LVGL to bind to.
|
||||
if (kernel_display_device != NULL && device_get_driver(kernel_display_device)->api == NULL) {
|
||||
device_put(kernel_display_device);
|
||||
kernel_display_device = NULL;
|
||||
}
|
||||
|
||||
if (kernel_display_device != NULL) {
|
||||
uint16_t vres = display_get_resolution_y(kernel_display_device);
|
||||
enum DisplayColorFormat color_format = display_get_color_format(kernel_display_device);
|
||||
bool swap_bytes = color_format == DISPLAY_COLOR_FORMAT_RGB565_SWAPPED ||
|
||||
color_format == DISPLAY_COLOR_FORMAT_BGR565_SWAPPED ||
|
||||
color_format == DISPLAY_COLOR_FORMAT_BGR565;
|
||||
bool display_requires_full_frame = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME);
|
||||
display_updates_slowly = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_SLOW_REFRESH);
|
||||
// Without CAP_SWAP_XY the driver can't rotate 90/270 in hardware (display_swap_xy() is
|
||||
// null and silently skipped by lvgl_display_apply_rotation()) - LVGL would still switch
|
||||
// its own logical w/h for those rotations, mismatching the panel's fixed physical
|
||||
// orientation (e.g. RGB/DPI panels, whose video timing is fixed at panel-init time).
|
||||
// sw_rotate makes LVGL rotate the rendered pixels in software instead, so the driver
|
||||
// itself is never asked to do something it can't.
|
||||
bool can_hw_rotate = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_SWAP_XY) &&
|
||||
display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_MIRROR);
|
||||
struct LvglDisplayConfig lvgl_display_config = {
|
||||
.buffer_height = vres > 10 ? vres / 10 : vres,
|
||||
.sw_rotate = !can_hw_rotate,
|
||||
.swap_bytes = swap_bytes,
|
||||
.force_full_frame = display_requires_full_frame
|
||||
};
|
||||
if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &lvgl_display) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_display_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_display_device->name);
|
||||
}
|
||||
device_put(kernel_display_device);
|
||||
}
|
||||
|
||||
struct Device* kernel_pointer_device = NULL;
|
||||
device_get_first_by_type(&POINTER_TYPE, &kernel_pointer_device);
|
||||
if (kernel_pointer_device != NULL && device_get_driver(kernel_pointer_device)->api == NULL) {
|
||||
device_put(kernel_pointer_device);
|
||||
kernel_pointer_device = NULL;
|
||||
}
|
||||
if (kernel_pointer_device != NULL) {
|
||||
lv_indev_t* lvgl_pointer_device;
|
||||
if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name);
|
||||
// Slow panels cause taps to be missed due to the long update time, prevent that
|
||||
if (display_updates_slowly ) {
|
||||
lv_indev_set_long_press_time(lvgl_pointer_device, 2000);
|
||||
}
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name);
|
||||
}
|
||||
device_put(kernel_pointer_device);
|
||||
}
|
||||
|
||||
struct Device* kernel_keyboard_device = NULL;
|
||||
device_get_first_by_type(&KEYBOARD_TYPE, &kernel_keyboard_device);
|
||||
lv_indev_t* lvgl_keyboard_device;
|
||||
if (kernel_keyboard_device != NULL) {
|
||||
if (lvgl_keyboard_add(kernel_keyboard_device, lvgl_display, &lvgl_keyboard_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_keyboard_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name);
|
||||
}
|
||||
device_put(kernel_keyboard_device);
|
||||
}
|
||||
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
void lvgl_devices_detach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_indev_t* device = lv_indev_get_next(NULL);
|
||||
while (device != NULL) {
|
||||
lv_indev_type_t type = lv_indev_get_type(device);
|
||||
if (type == LV_INDEV_TYPE_POINTER) {
|
||||
lvgl_pointer_remove(device);
|
||||
} else if (type == LV_INDEV_TYPE_KEYPAD) {
|
||||
lvgl_keyboard_remove(device);
|
||||
} else {
|
||||
lv_indev_delete(device);
|
||||
}
|
||||
// Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
|
||||
device = lv_indev_get_next(NULL);
|
||||
}
|
||||
|
||||
lv_disp_t* display = lv_disp_get_next(NULL);
|
||||
while (display != NULL) {
|
||||
lvgl_display_remove(display);
|
||||
display = lv_disp_get_next(NULL);
|
||||
}
|
||||
|
||||
lvgl_unlock();
|
||||
}
|
||||
185
Modules/lvgl-module/source/devices/devices.cpp
Normal file
185
Modules/lvgl-module/source/devices/devices.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
#include <lvgl/devices/display.h>
|
||||
#include <lvgl/devices/device_context.h>
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
#include <lvgl/devices/pointer.h>
|
||||
#include <lvgl/devices/trackball.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
#include <tactility/drivers/trackball.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
constexpr auto* TAG = "lvgl";
|
||||
|
||||
// Boards are not expected to expose more devices of a single type than this; device_for_each_of_type()
|
||||
// callbacks run under the device ledger lock, so devices are collected here and processed afterwards.
|
||||
constexpr auto LVGL_DEVICES_MAX_PER_TYPE = 8;
|
||||
|
||||
struct LvglDeviceList {
|
||||
struct Device* devices[LVGL_DEVICES_MAX_PER_TYPE];
|
||||
size_t count;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static bool lvgl_device_list_collect(struct Device* device, void* context) {
|
||||
struct LvglDeviceList* list = (struct LvglDeviceList*)context;
|
||||
if (list->count < LVGL_DEVICES_MAX_PER_TYPE) {
|
||||
list->devices[list->count++] = device;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void lvgl_devices_attach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_disp_t* lvgl_display = NULL;
|
||||
bool display_updates_slowly = false;
|
||||
|
||||
struct LvglDeviceList display_devices = {0};
|
||||
device_for_each_of_type(&DISPLAY_TYPE, &display_devices, lvgl_device_list_collect);
|
||||
for (size_t i = 0; i < display_devices.count; i++) {
|
||||
struct Device* kernel_display_device = display_devices.devices[i];
|
||||
|
||||
// Placeholder drivers (boards not yet migrated to the kernel display driver) register with a
|
||||
// NULL api: they exist so the devicetree node resolves, but have nothing for LVGL to bind to.
|
||||
if (device_get_driver(kernel_display_device)->api == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t vres = display_get_resolution_y(kernel_display_device);
|
||||
enum DisplayColorFormat color_format = display_get_color_format(kernel_display_device);
|
||||
bool swap_bytes = color_format == DISPLAY_COLOR_FORMAT_RGB565_SWAPPED ||
|
||||
color_format == DISPLAY_COLOR_FORMAT_BGR565_SWAPPED ||
|
||||
color_format == DISPLAY_COLOR_FORMAT_BGR565;
|
||||
bool display_requires_full_frame = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME);
|
||||
// Without CAP_SWAP_XY the driver can't rotate 90/270 in hardware (display_swap_xy() is
|
||||
// null and silently skipped by lvgl_display_apply_rotation()) - LVGL would still switch
|
||||
// its own logical w/h for those rotations, mismatching the panel's fixed physical
|
||||
// orientation (e.g. RGB/DPI panels, whose video timing is fixed at panel-init time).
|
||||
// sw_rotate makes LVGL rotate the rendered pixels in software instead, so the driver
|
||||
// itself is never asked to do something it can't.
|
||||
bool can_hw_rotate = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_SWAP_XY) &&
|
||||
display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_MIRROR);
|
||||
struct LvglDisplayConfig lvgl_display_config = {
|
||||
.buffer_height = (uint16_t)(vres > 10 ? vres / 10 : vres),
|
||||
.sw_rotate = !can_hw_rotate,
|
||||
.swap_bytes = swap_bytes,
|
||||
.force_full_frame = display_requires_full_frame
|
||||
};
|
||||
lv_disp_t* added_display = NULL;
|
||||
if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &added_display) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_display_device->name);
|
||||
// Pointers/keyboards below bind to the first display bound here, matching that display's
|
||||
// refresh behavior.
|
||||
if (lvgl_display == NULL) {
|
||||
lvgl_display = added_display;
|
||||
display_updates_slowly = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_SLOW_REFRESH);
|
||||
}
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_display_device->name);
|
||||
}
|
||||
}
|
||||
|
||||
struct LvglDeviceList pointer_devices = {0};
|
||||
device_for_each_of_type(&POINTER_TYPE, &pointer_devices, lvgl_device_list_collect);
|
||||
for (size_t i = 0; i < pointer_devices.count; i++) {
|
||||
struct Device* kernel_pointer_device = pointer_devices.devices[i];
|
||||
if (device_get_driver(kernel_pointer_device)->api == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lv_indev_t* lvgl_pointer_device;
|
||||
if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name);
|
||||
// Slow panels cause taps to be missed due to the long update time, prevent that
|
||||
if (display_updates_slowly ) {
|
||||
lv_indev_set_long_press_time(lvgl_pointer_device, 2000);
|
||||
}
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name);
|
||||
}
|
||||
}
|
||||
|
||||
struct LvglDeviceList keyboard_devices = {0};
|
||||
device_for_each_of_type(&KEYBOARD_TYPE, &keyboard_devices, lvgl_device_list_collect);
|
||||
for (size_t i = 0; i < keyboard_devices.count; i++) {
|
||||
struct Device* kernel_keyboard_device = keyboard_devices.devices[i];
|
||||
lv_indev_t* lvgl_keyboard_device;
|
||||
if (lvgl_keyboard_add(kernel_keyboard_device, lvgl_display, &lvgl_keyboard_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_keyboard_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name);
|
||||
}
|
||||
}
|
||||
|
||||
struct Device* kernel_trackball_device = NULL;
|
||||
device_get_first_by_type(&TRACKBALL_TYPE, &kernel_trackball_device);
|
||||
if (kernel_trackball_device != NULL) {
|
||||
lv_indev_t* lvgl_trackball_device;
|
||||
if (lvgl_trackball_add(kernel_trackball_device, lvgl_display, &lvgl_trackball_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_trackball_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_trackball_device->name);
|
||||
}
|
||||
device_put(kernel_trackball_device);
|
||||
}
|
||||
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
void lvgl_devices_detach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_indev_t* indev = lv_indev_get_next(NULL);
|
||||
while (indev != NULL) {
|
||||
lv_indev_type_t type = lv_indev_get_type(indev);
|
||||
if (type == LV_INDEV_TYPE_POINTER || type == LV_INDEV_TYPE_ENCODER) {
|
||||
bool handled = false;
|
||||
void* driver_data = lv_indev_get_driver_data(indev);
|
||||
if (driver_data != nullptr) {
|
||||
// Trackball is a special pointer type as it can operate as a mouse or as a device that changes widget focus.
|
||||
LvglDeviceContext* context = static_cast<LvglDeviceContext*>(driver_data);
|
||||
if (context->device != nullptr) {
|
||||
const DeviceType* device_type = device_get_type(context->device);
|
||||
if (device_type == &TRACKBALL_TYPE) {
|
||||
lvgl_trackball_remove(indev);
|
||||
handled = true;
|
||||
} else if (device_type == &POINTER_TYPE) {
|
||||
lvgl_pointer_remove(indev);
|
||||
handled = true;
|
||||
} else {
|
||||
LOG_E(TAG, "Unknown pointer device with possible memory leak of driver data");
|
||||
}
|
||||
} else {
|
||||
LOG_W(TAG, "Unknown pointer device (no data attached)");
|
||||
}
|
||||
}
|
||||
if (!handled) {
|
||||
lv_indev_delete(indev);
|
||||
}
|
||||
} else if (type == LV_INDEV_TYPE_KEYPAD) {
|
||||
lvgl_keyboard_remove(indev);
|
||||
} else {
|
||||
lv_indev_delete(indev);
|
||||
}
|
||||
// Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
|
||||
indev = lv_indev_get_next(NULL);
|
||||
}
|
||||
|
||||
lv_disp_t* display = lv_disp_get_next(NULL);
|
||||
while (display != NULL) {
|
||||
lvgl_display_remove(display);
|
||||
display = lv_disp_get_next(NULL);
|
||||
}
|
||||
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
} // extern C
|
||||
@ -8,16 +8,17 @@
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl/devices/device_context.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
|
||||
#define TAG "lvgl_display"
|
||||
constexpr auto* TAG = "lvgl_display";
|
||||
|
||||
struct LvglDisplayCtx {
|
||||
struct Device* device;
|
||||
void* buf1;
|
||||
void* buf2;
|
||||
bool owns_buffers; // false when buf1/buf2 point at the device's own frame buffer(s)
|
||||
@ -108,7 +109,9 @@ static bool lvgl_display_map_color_format(enum DisplayColorFormat in, lv_color_f
|
||||
}
|
||||
}
|
||||
|
||||
static void lvgl_display_apply_rotation(struct LvglDisplayCtx* ctx, lv_display_rotation_t rotation) {
|
||||
static void lvgl_display_apply_rotation(struct LvglDeviceContext* wrapper, lv_display_rotation_t rotation) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)wrapper->context;
|
||||
|
||||
// SW-rotated displays stay in their base orientation; rotation is applied per-flush instead.
|
||||
if (ctx->sw_rotate) {
|
||||
return;
|
||||
@ -144,10 +147,10 @@ static void lvgl_display_apply_rotation(struct LvglDisplayCtx* ctx, lv_display_r
|
||||
}
|
||||
|
||||
if (ctx->has_swap_xy_cap) {
|
||||
display_swap_xy(ctx->device, swap_xy);
|
||||
display_swap_xy(wrapper->device, swap_xy);
|
||||
}
|
||||
if (ctx->has_mirror_cap) {
|
||||
display_mirror(ctx->device, mirror_x, mirror_y);
|
||||
display_mirror(wrapper->device, mirror_x, mirror_y);
|
||||
}
|
||||
if (ctx->has_set_gap_cap) {
|
||||
// set_gap() takes its (x,y) in whatever axes the panel is currently drawn with, not the
|
||||
@ -155,14 +158,14 @@ static void lvgl_display_apply_rotation(struct LvglDisplayCtx* ctx, lv_display_r
|
||||
bool gap_axes_swapped = swap_xy != ctx->base_swap_xy;
|
||||
int32_t gap_x = gap_axes_swapped ? ctx->base_gap_y : ctx->base_gap_x;
|
||||
int32_t gap_y = gap_axes_swapped ? ctx->base_gap_x : ctx->base_gap_y;
|
||||
display_set_gap(ctx->device, gap_x, gap_y);
|
||||
display_set_gap(wrapper->device, gap_x, gap_y);
|
||||
}
|
||||
}
|
||||
|
||||
static void lvgl_display_rotation_event_cb(lv_event_t* e) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_event_get_user_data(e);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_event_get_user_data(e);
|
||||
lv_display_t* disp = (lv_display_t*)lv_event_get_current_target(e);
|
||||
lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp));
|
||||
lvgl_display_apply_rotation(wrapper, lv_display_get_rotation(disp));
|
||||
}
|
||||
|
||||
// Returns which of buf1/buf2 (the real frame buffers, when !owns_buffers) color_map falls inside.
|
||||
@ -206,7 +209,8 @@ static void* lvgl_display_try_ppa_rotate(struct LvglDisplayCtx* ctx, const uint8
|
||||
}
|
||||
|
||||
static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_map) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(disp);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_display_get_driver_data(disp);
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)wrapper->context;
|
||||
bool is_i1 = lv_display_get_color_format(disp) == LV_COLOR_FORMAT_I1;
|
||||
|
||||
int32_t x1 = area->x1;
|
||||
@ -280,8 +284,8 @@ static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uin
|
||||
} else {
|
||||
fb_base = (uint8_t*)lvgl_display_fb_base(ctx, color_map);
|
||||
}
|
||||
uint16_t hres = display_get_resolution_x(ctx->device);
|
||||
uint16_t vres = display_get_resolution_y(ctx->device);
|
||||
uint16_t hres = display_get_resolution_x(wrapper->device);
|
||||
uint16_t vres = display_get_resolution_y(wrapper->device);
|
||||
|
||||
if (rotating) {
|
||||
// fb_base now holds the whole completed frame, but still in LVGL's *logical*
|
||||
@ -303,13 +307,13 @@ static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uin
|
||||
}
|
||||
}
|
||||
|
||||
display_draw_bitmap(ctx->device, 0, 0, hres, vres, fb_base);
|
||||
display_draw_bitmap(wrapper->device, 0, 0, hres, vres, fb_base);
|
||||
}
|
||||
} else if (ctx->owns_buffers) {
|
||||
// PARTIAL mode: each flush_cb call is one independent, complete tile into a buffer that
|
||||
// gets reused for the next tile, so present it immediately rather than waiting.
|
||||
// LVGL's area is inclusive; DisplayApi's draw_bitmap wants an exclusive end.
|
||||
display_draw_bitmap(ctx->device, x1, y1, x2 + 1, y2 + 1, color_map);
|
||||
display_draw_bitmap(wrapper->device, x1, y1, x2 + 1, y2 + 1, color_map);
|
||||
}
|
||||
// DisplayApi has no async completion callback, so draw_bitmap is synchronous.
|
||||
lv_display_flush_ready(disp);
|
||||
@ -335,11 +339,17 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
uint8_t fb_count = display_get_frame_buffer_count(device);
|
||||
uint8_t bpp = lv_color_format_get_size(lv_color_format);
|
||||
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)calloc(1, sizeof(struct LvglDisplayCtx));
|
||||
struct LvglDisplayCtx* ctx = new(std::nothrow) LvglDisplayCtx();
|
||||
if (ctx == NULL) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->device = device;
|
||||
struct LvglDeviceContext* wrapper = new(std::nothrow) LvglDeviceContext(ctx);
|
||||
if (wrapper == NULL) {
|
||||
delete ctx;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
wrapper->device = device;
|
||||
|
||||
ctx->byte_swap = config->swap_bytes;
|
||||
ctx->sw_rotate = config->sw_rotate;
|
||||
// Only relevant when sw_rotate is set - lvgl_display_try_ppa_rotate() also checks
|
||||
@ -388,7 +398,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
buf_size_bytes = (size_t)((hres + 7) / 8) * vres + 8;
|
||||
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes);
|
||||
if (ctx->buf1 == NULL) {
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->owns_buffers = true;
|
||||
@ -408,14 +418,14 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
|
||||
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes);
|
||||
if (ctx->buf1 == NULL) {
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
if (config->double_buffer) {
|
||||
ctx->buf2 = lvgl_display_alloc_buffer(buf_size_bytes);
|
||||
if (ctx->buf2 == NULL) {
|
||||
lvgl_display_free_buffer(ctx->buf1);
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
@ -432,7 +442,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
lvgl_display_free_buffer(ctx->buf1);
|
||||
lvgl_display_free_buffer(ctx->buf2);
|
||||
}
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
@ -444,7 +454,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
lvgl_display_free_buffer(ctx->buf2);
|
||||
}
|
||||
lvgl_display_free_buffer(ctx->rotate_buf);
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
@ -452,11 +462,11 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
lv_display_set_color_format(disp, lv_color_format);
|
||||
lv_display_set_buffers(disp, ctx->buf1, ctx->buf2, buf_size_bytes, render_mode);
|
||||
lv_display_set_flush_cb(disp, lvgl_display_flush_cb);
|
||||
lv_display_set_driver_data(disp, ctx);
|
||||
lv_display_add_event_cb(disp, lvgl_display_rotation_event_cb, LV_EVENT_RESOLUTION_CHANGED, ctx);
|
||||
lv_display_set_driver_data(disp, wrapper);
|
||||
lv_display_add_event_cb(disp, lvgl_display_rotation_event_cb, LV_EVENT_RESOLUTION_CHANGED, wrapper);
|
||||
|
||||
// Apply once explicitly, independent of whether LV_EVENT_RESOLUTION_CHANGED fires on creation.
|
||||
lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp));
|
||||
lvgl_display_apply_rotation(wrapper, lv_display_get_rotation(disp));
|
||||
|
||||
*out_display = disp;
|
||||
return ERROR_NONE;
|
||||
@ -467,10 +477,11 @@ void lvgl_display_remove(lv_display_t* display) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(display);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_display_get_driver_data(display);
|
||||
lv_display_delete(display);
|
||||
|
||||
if (ctx != NULL) {
|
||||
if (wrapper != NULL) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)wrapper->context;
|
||||
if (ctx->owns_buffers) {
|
||||
if (ctx->buf1 != NULL) {
|
||||
lvgl_display_free_buffer(ctx->buf1);
|
||||
@ -485,6 +496,6 @@ void lvgl_display_remove(lv_display_t* display) {
|
||||
if (ctx->ppa_handle != NULL) {
|
||||
lvgl_ppa_delete(ctx->ppa_handle);
|
||||
}
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
#include <lvgl/devices/device_context.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <vector>
|
||||
|
||||
struct LvglKeyboardCtx {
|
||||
Device* device;
|
||||
};
|
||||
|
||||
static LvglSoftwareKeyboard last_software_keyboard = {
|
||||
.object = nullptr
|
||||
};
|
||||
@ -39,10 +35,10 @@ void lvgl_keyboard_on_stop_lvgl() {
|
||||
}
|
||||
|
||||
static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
LvglKeyboardCtx* ctx = static_cast<struct LvglKeyboardCtx*>(lv_indev_get_driver_data(indev));
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
|
||||
KeyboardKeyData key_data = {};
|
||||
if (keyboard_read_key(ctx->device, &key_data) != ERROR_NONE) {
|
||||
if (keyboard_read_key(wrapper->device, &key_data) != ERROR_NONE) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
data->continue_reading = false;
|
||||
return;
|
||||
@ -62,21 +58,21 @@ error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
LvglKeyboardCtx* ctx = static_cast<struct LvglKeyboardCtx*>(malloc(sizeof(struct LvglKeyboardCtx)));
|
||||
if (ctx == NULL) {
|
||||
auto* wrapper = new(std::nothrow) LvglDeviceContext(nullptr);
|
||||
if (wrapper == NULL) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->device = device;
|
||||
wrapper->device = device;
|
||||
|
||||
lv_indev_t* indev = lv_indev_create();
|
||||
if (indev == NULL) {
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
|
||||
lv_indev_set_read_cb(indev, lvgl_keyboard_read_cb);
|
||||
lv_indev_set_driver_data(indev, ctx);
|
||||
lv_indev_set_driver_data(indev, wrapper);
|
||||
if (display != NULL) {
|
||||
lv_indev_set_display(indev, display);
|
||||
}
|
||||
@ -90,9 +86,9 @@ void lvgl_keyboard_remove(lv_indev_t* indev) {
|
||||
return;
|
||||
}
|
||||
|
||||
LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev);
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
lv_indev_delete(indev);
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
}
|
||||
|
||||
void lvgl_keyboard_enable(lv_indev_t* indev) {
|
||||
@ -115,22 +111,21 @@ bool lvgl_hardware_keyboard_is_available() {
|
||||
}
|
||||
|
||||
void lvgl_hardware_keyboard_add_custom(lv_indev_t* indev) {
|
||||
LvglKeyboardCtx* ctx = static_cast<struct LvglKeyboardCtx*>(malloc(sizeof(struct LvglKeyboardCtx)));
|
||||
if (ctx == nullptr) {
|
||||
auto* wrapper = new(std::nothrow) LvglDeviceContext(nullptr);
|
||||
if (wrapper == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->device = nullptr;
|
||||
lv_indev_set_driver_data(indev, ctx);
|
||||
lv_indev_set_driver_data(indev, wrapper);
|
||||
|
||||
lvgl_keyboard_enable(indev);
|
||||
}
|
||||
|
||||
void lvgl_hardware_keyboard_remove_custom(lv_indev_t* indev) {
|
||||
lvgl_keyboard_disable(indev);
|
||||
auto* data = lv_indev_get_driver_data(indev);
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
lv_indev_set_driver_data(indev, nullptr);
|
||||
free(data); // LvglKeyboardCtx*
|
||||
delete wrapper;
|
||||
}
|
||||
|
||||
static void textarea_show_keyboard(lv_event_t* event) {
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/devices/pointer.h>
|
||||
#include <lvgl/devices/device_context.h>
|
||||
|
||||
#include <tactility/drivers/pointer.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define TAG "lvgl_pointer"
|
||||
constexpr auto* TAG = "lvgl_pointer";
|
||||
|
||||
struct LvglPointerCtx {
|
||||
struct Device* device;
|
||||
bool calibration_enabled;
|
||||
struct LvglPointerCalibration calibration;
|
||||
};
|
||||
@ -60,13 +58,13 @@ static void lvgl_pointer_calibration_apply(
|
||||
// fixed pixel dimensions, not a rotation. This function has no notion of LVGL rotation at all:
|
||||
// calibration corrects the raw sensor's fixed physical mapping, which never changes with on-screen
|
||||
// orientation, so it doesn't belong anywhere near rotation math.
|
||||
static bool lvgl_pointer_read_calibrated(struct LvglPointerCtx* ctx, int32_t native_x_max, int32_t native_y_max, uint16_t* x, uint16_t* y) {
|
||||
if (pointer_read_data(ctx->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
|
||||
static bool lvgl_pointer_read_calibrated(struct Device* device, struct LvglPointerCtx* ctx, int32_t native_x_max, int32_t native_y_max, uint16_t* x, uint16_t* y) {
|
||||
if (pointer_read_data(device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t point_count = 0;
|
||||
if (!pointer_get_touched_points(ctx->device, x, y, NULL, &point_count, 1) || point_count == 0) {
|
||||
if (!pointer_get_touched_points(device, x, y, NULL, &point_count, 1) || point_count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -82,7 +80,8 @@ static bool lvgl_pointer_read_calibrated(struct LvglPointerCtx* ctx, int32_t nat
|
||||
// logical space - unconditionally, since native-space coordinates always need this regardless of
|
||||
// whether calibration is enabled.
|
||||
static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)wrapper->context;
|
||||
lv_display_t* display = lv_indev_get_display(indev);
|
||||
|
||||
// lv_display_get_original_*_resolution() is the native (LV_DISPLAY_ROTATION_0) size,
|
||||
@ -92,7 +91,7 @@ static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
|
||||
uint16_t x = 0;
|
||||
uint16_t y = 0;
|
||||
if (!lvgl_pointer_read_calibrated(ctx, native_x_max, native_y_max, &x, &y)) {
|
||||
if (!lvgl_pointer_read_calibrated(wrapper->device, ctx, native_x_max, native_y_max, &x, &y)) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
return;
|
||||
}
|
||||
@ -110,21 +109,26 @@ error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
struct LvglPointerCtx* ctx = calloc(1, sizeof(struct LvglPointerCtx));
|
||||
struct LvglPointerCtx* ctx = new(std::nothrow) LvglPointerCtx();
|
||||
if (ctx == NULL) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->device = device;
|
||||
struct LvglDeviceContext* wrapper = new(std::nothrow) LvglDeviceContext(ctx);
|
||||
if (wrapper == NULL) {
|
||||
delete ctx;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
wrapper->device = device;
|
||||
|
||||
lv_indev_t* indev = lv_indev_create();
|
||||
if (indev == NULL) {
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_read_cb(indev, lvgl_pointer_read_cb);
|
||||
lv_indev_set_driver_data(indev, ctx);
|
||||
lv_indev_set_driver_data(indev, wrapper);
|
||||
if (display != NULL) {
|
||||
lv_indev_set_display(indev, display);
|
||||
}
|
||||
@ -145,7 +149,8 @@ error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointer
|
||||
if (indev == NULL) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)wrapper->context;
|
||||
|
||||
if (calibration == NULL) {
|
||||
ctx->calibration_enabled = false;
|
||||
@ -164,7 +169,8 @@ bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibrati
|
||||
if (indev == NULL || out_calibration == NULL) {
|
||||
return false;
|
||||
}
|
||||
struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)wrapper->context;
|
||||
if (!ctx->calibration_enabled) {
|
||||
return false;
|
||||
}
|
||||
@ -177,10 +183,10 @@ void lvgl_pointer_remove(lv_indev_t* indev) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||
if (default_pointer_indev == indev) {
|
||||
default_pointer_indev = NULL;
|
||||
}
|
||||
lv_indev_delete(indev);
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
}
|
||||
255
Modules/lvgl-module/source/devices/trackball.cpp
Normal file
255
Modules/lvgl-module/source/devices/trackball.cpp
Normal file
@ -0,0 +1,255 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/devices/trackball.h>
|
||||
#include <lvgl/devices/device_context.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <tactility/drivers/trackball.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr auto* TAG = "lvgl_trackball";
|
||||
|
||||
struct LvglTrackballCtx {
|
||||
LvglTrackballSettings settings;
|
||||
int32_t cursor_x;
|
||||
int32_t cursor_y;
|
||||
lv_obj_t* cursor;
|
||||
const void* cursor_image_src;
|
||||
};
|
||||
|
||||
static inline int32_t clamp(int32_t value, int32_t min_value, int32_t max_value) {
|
||||
if (value < min_value) return min_value;
|
||||
if (value > max_value) return max_value;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void recenter_cursor(LvglTrackballCtx* ctx, lv_indev_t* indev) {
|
||||
lv_display_t* display = lv_indev_get_display(indev);
|
||||
// lv_display_get_original_*_resolution() is the native (LV_DISPLAY_ROTATION_0) size,
|
||||
// unaffected by the display's current rotation, matching lvgl/devices/pointer.c's approach.
|
||||
ctx->cursor_x = display != nullptr ? lv_display_get_original_horizontal_resolution(display) / 2 : 0;
|
||||
ctx->cursor_y = display != nullptr ? lv_display_get_original_vertical_resolution(display) / 2 : 0;
|
||||
}
|
||||
|
||||
// Creates the cursor object on first use and binds it to indev via lv_indev_set_cursor() exactly
|
||||
// once - LVGL's lv_indev_set_cursor() unconditionally reparents whatever it's given (even NULL:
|
||||
// it does not special-case that, see hide_cursor() below), so it must only ever be called here,
|
||||
// with a freshly-created, non-null object whose parent already matches (lv_layer_sys()), making
|
||||
// lv_obj_set_parent()'s "parent == obj->parent" check a safe no-op. Once bound, later image/
|
||||
// enabled changes update the existing object in place instead of re-binding.
|
||||
static void show_cursor(LvglTrackballCtx* ctx, lv_indev_t* indev) {
|
||||
if (ctx->cursor_image_src == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->cursor == nullptr) {
|
||||
ctx->cursor = lv_image_create(lv_layer_sys());
|
||||
if (ctx->cursor == nullptr) {
|
||||
return;
|
||||
}
|
||||
lv_obj_remove_flag(ctx->cursor, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_indev_set_cursor(indev, ctx->cursor);
|
||||
}
|
||||
|
||||
lv_image_set_src(ctx->cursor, ctx->cursor_image_src);
|
||||
if (ctx->settings.enabled) {
|
||||
lv_obj_remove_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
// Only hides the cursor object - never deletes it
|
||||
static void hide_cursor(LvglTrackballCtx* ctx) {
|
||||
if (ctx->cursor == nullptr) {
|
||||
return;
|
||||
}
|
||||
lv_obj_add_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
static void lvgl_trackball_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
|
||||
// Always drain accumulated movement so it doesn't jump on re-enable, but discard it while disabled.
|
||||
int32_t dx = 0;
|
||||
int32_t dy = 0;
|
||||
trackball_read_delta(wrapper->device, &dx, &dy);
|
||||
if (!ctx->settings.enabled) {
|
||||
dx = 0;
|
||||
dy = 0;
|
||||
}
|
||||
|
||||
lv_display_t* display = lv_indev_get_display(indev);
|
||||
|
||||
if (ctx->settings.mode == LVGL_TRACKBALL_MODE_ENCODER) {
|
||||
int32_t ticks = (dx + dy) * static_cast<int32_t>(ctx->settings.encoder_sensitivity);
|
||||
data->enc_diff = static_cast<int16_t>(clamp(ticks, INT16_MIN, INT16_MAX));
|
||||
if (ticks != 0) {
|
||||
lv_display_trigger_activity(display);
|
||||
}
|
||||
} else {
|
||||
int32_t max_x = display != nullptr ? lv_display_get_original_horizontal_resolution(display) - 1 : 0;
|
||||
int32_t max_y = display != nullptr ? lv_display_get_original_vertical_resolution(display) - 1 : 0;
|
||||
ctx->cursor_x = clamp(ctx->cursor_x + dx * static_cast<int32_t>(ctx->settings.pointer_sensitivity), 0, max_x);
|
||||
ctx->cursor_y = clamp(ctx->cursor_y + dy * static_cast<int32_t>(ctx->settings.pointer_sensitivity), 0, max_y);
|
||||
data->point.x = static_cast<int16_t>(ctx->cursor_x);
|
||||
data->point.y = static_cast<int16_t>(ctx->cursor_y);
|
||||
}
|
||||
|
||||
bool pressed = false;
|
||||
if (ctx->settings.enabled) {
|
||||
trackball_get_button_pressed(wrapper->device, &pressed);
|
||||
}
|
||||
data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
|
||||
if (pressed) {
|
||||
lv_display_trigger_activity(display);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
LvglTrackballSettings lvgl_trackball_settings_get_default() {
|
||||
return LvglTrackballSettings {
|
||||
.mode = LVGL_TRACKBALL_MODE_ENCODER,
|
||||
.enabled = true,
|
||||
.encoder_sensitivity = 1,
|
||||
.pointer_sensitivity = 10,
|
||||
};
|
||||
}
|
||||
|
||||
error_t lvgl_trackball_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) {
|
||||
if (device == nullptr || out_indev == nullptr) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (device_get_type(device) != &TRACKBALL_TYPE) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto* ctx = new(std::nothrow) LvglTrackballCtx();
|
||||
if (ctx == nullptr) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->settings = lvgl_trackball_settings_get_default();
|
||||
|
||||
auto* wrapper = new(std::nothrow) LvglDeviceContext(ctx);
|
||||
if (wrapper == nullptr) {
|
||||
delete ctx;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
wrapper->device = device;
|
||||
|
||||
lv_indev_t* indev = lv_indev_create();
|
||||
if (indev == nullptr) {
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
|
||||
lv_indev_set_read_cb(indev, lvgl_trackball_read_cb);
|
||||
lv_indev_set_driver_data(indev, wrapper);
|
||||
if (display != nullptr) {
|
||||
lv_indev_set_display(indev, display);
|
||||
}
|
||||
recenter_cursor(ctx, indev);
|
||||
|
||||
*out_indev = indev;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void lvgl_trackball_remove(lv_indev_t* indev) {
|
||||
if (indev == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
check(wrapper);
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
check(ctx);
|
||||
if (ctx->cursor != nullptr) {
|
||||
lv_obj_delete(ctx->cursor);
|
||||
}
|
||||
lv_indev_delete(indev);
|
||||
delete wrapper;
|
||||
}
|
||||
|
||||
error_t lvgl_trackball_set_settings(lv_indev_t* indev, const struct LvglTrackballSettings* settings) {
|
||||
if (indev == nullptr || settings == nullptr) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (
|
||||
(settings->mode != LVGL_TRACKBALL_MODE_ENCODER && settings->mode != LVGL_TRACKBALL_MODE_POINTER) ||
|
||||
settings->encoder_sensitivity == 0 ||
|
||||
settings->pointer_sensitivity == 0
|
||||
) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
check(wrapper);
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
check(ctx);
|
||||
bool mode_changed = ctx->settings.mode != settings->mode;
|
||||
ctx->settings = *settings;
|
||||
|
||||
if (mode_changed) {
|
||||
if (settings->mode == LVGL_TRACKBALL_MODE_POINTER) {
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||
recenter_cursor(ctx, indev);
|
||||
show_cursor(ctx, indev);
|
||||
} else {
|
||||
hide_cursor(ctx);
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor visibility only tracks the enabled toggle in pointer mode - in encoder mode it must
|
||||
// stay hidden regardless of enabled, otherwise this unconditionally un-hides the cursor
|
||||
// hide_cursor() just hid above (enabled is independent of mode, and defaults to true).
|
||||
if (ctx->cursor != nullptr && ctx->settings.mode == LVGL_TRACKBALL_MODE_POINTER) {
|
||||
if (ctx->settings.enabled) {
|
||||
lv_obj_remove_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
bool lvgl_trackball_get_settings(lv_indev_t* indev, struct LvglTrackballSettings* out_settings) {
|
||||
if (indev == nullptr || out_settings == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
check(wrapper);
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
check(ctx);
|
||||
*out_settings = ctx->settings;
|
||||
return true;
|
||||
}
|
||||
|
||||
void lvgl_trackball_set_cursor_image(lv_indev_t* indev, const void* image_src) {
|
||||
if (indev == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
check(wrapper);
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
check(ctx);
|
||||
ctx->cursor_image_src = image_src;
|
||||
|
||||
if (ctx->settings.mode == LVGL_TRACKBALL_MODE_POINTER) {
|
||||
if (image_src == nullptr) {
|
||||
hide_cursor(ctx);
|
||||
} else {
|
||||
show_cursor(ctx, indev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@ -1,27 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <lvgl/devices/trackball.h>
|
||||
|
||||
namespace tt::settings::trackball {
|
||||
|
||||
enum class TrackballMode : uint8_t {
|
||||
Encoder = 0, // Scroll wheel navigation (default)
|
||||
Pointer = 1 // Mouse cursor mode
|
||||
};
|
||||
bool load(LvglTrackballSettings& settings);
|
||||
|
||||
struct TrackballSettings {
|
||||
bool trackballEnabled = false;
|
||||
TrackballMode trackballMode = TrackballMode::Encoder;
|
||||
uint8_t encoderSensitivity = 1; // Steps per tick (1-10)
|
||||
uint8_t pointerSensitivity = 10; // Pixels per tick (1-10)
|
||||
};
|
||||
LvglTrackballSettings loadOrGetDefault();
|
||||
|
||||
bool load(TrackballSettings& settings);
|
||||
LvglTrackballSettings getDefault();
|
||||
|
||||
TrackballSettings loadOrGetDefault();
|
||||
|
||||
TrackballSettings getDefault();
|
||||
|
||||
bool save(const TrackballSettings& settings);
|
||||
bool save(const LvglTrackballSettings& settings);
|
||||
|
||||
}
|
||||
|
||||
10
Tactility/Private/Tactility/lvgl/TrackballInit.h
Normal file
10
Tactility/Private/Tactility/lvgl/TrackballInit.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
/**
|
||||
* Applies trackball settings (either stored or default ones) to all trackball devices.
|
||||
*/
|
||||
void initTrackball();
|
||||
|
||||
}
|
||||
@ -12,8 +12,8 @@
|
||||
#include <Tactility/app/AppManifestParsing.h>
|
||||
#include <Tactility/app/AppRegistration.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/FileLock.h>
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/lvgl/TrackballInit.h>
|
||||
#include <Tactility/hal/SdCard.h>
|
||||
#include <Tactility/network/NtpPrivate.h>
|
||||
#include <Tactility/Paths.h>
|
||||
@ -34,11 +34,13 @@
|
||||
#include <lvgl/widgets/toolbar.h>
|
||||
|
||||
#include <tactility/concurrent/thread.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/audio_stream.h>
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/drivers/grove.h>
|
||||
#include <tactility/drivers/power_supply.h>
|
||||
#include <tactility/drivers/rtc.h>
|
||||
#include <tactility/drivers/trackball.h>
|
||||
#include <tactility/drivers/uart_controller.h>
|
||||
#include <tactility/filesystem/file_mutex.h>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
@ -154,9 +156,10 @@ namespace app {
|
||||
namespace webserversettings { extern const AppManifest manifest; }
|
||||
#if CONFIG_TT_TDECK_WORKAROUND == 1
|
||||
namespace keyboardsettings { extern const AppManifest manifest; } // T-Deck only for now
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace trackballsettings { extern const AppManifest manifest; } // T-Deck only for now
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||
namespace screenshot { extern const AppManifest manifest; }
|
||||
@ -216,9 +219,12 @@ static void registerInternalApps() {
|
||||
addAppManifest(app::development::manifest);
|
||||
#if defined(CONFIG_TT_TDECK_WORKAROUND)
|
||||
addAppManifest(app::keyboardsettings::manifest);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (device_exists_of_type(&TRACKBALL_TYPE)) {
|
||||
addAppManifest(app::trackballsettings::manifest);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(CONFIG_TINYUSB_MSC_ENABLED) && CONFIG_TINYUSB_MSC_ENABLED
|
||||
addAppManifest(app::usbsettings::manifest);
|
||||
@ -373,6 +379,8 @@ static void onLvglStarted() {
|
||||
addService(service::screenshot::manifest);
|
||||
#endif
|
||||
|
||||
lvgl::initTrackball();
|
||||
|
||||
memory_print_stats();
|
||||
}
|
||||
|
||||
|
||||
@ -1,59 +1,61 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <lvgl/devices/device_context.h>
|
||||
#include <lvgl/devices/trackball.h>
|
||||
#include <lvgl/icons/shared.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <tactility/drivers/trackball.h>
|
||||
|
||||
#include <Tactility/Assets.h>
|
||||
#include <Tactility/settings/TrackballSettings.h>
|
||||
#include <Tactility/lvgl/Toolbar.h>
|
||||
|
||||
// Forward declare driver functions
|
||||
namespace trackball {
|
||||
void setEnabled(bool enabled);
|
||||
enum class Mode { Encoder, Pointer };
|
||||
void setMode(Mode mode);
|
||||
void setEncoderSensitivity(uint8_t sensitivity);
|
||||
void setPointerSensitivity(uint8_t sensitivity);
|
||||
}
|
||||
#include <Tactility/Tactility.h>
|
||||
|
||||
namespace tt::app::trackballsettings {
|
||||
|
||||
constexpr auto* TAG = "TrackballSettings";
|
||||
|
||||
static trackball::Mode toDriverMode(settings::trackball::TrackballMode mode) {
|
||||
// Convert mode to dropdown index (dropdown order: Encoder=0, Pointer=1)
|
||||
static uint32_t modeToDropdownIndex(LvglTrackballMode mode) {
|
||||
switch (mode) {
|
||||
case settings::trackball::TrackballMode::Encoder: return trackball::Mode::Encoder;
|
||||
case settings::trackball::TrackballMode::Pointer: return trackball::Mode::Pointer;
|
||||
}
|
||||
return trackball::Mode::Encoder; // default
|
||||
}
|
||||
|
||||
// Convert settings enum to dropdown index (dropdown order: Encoder=0, Pointer=1)
|
||||
static uint32_t modeToDropdownIndex(settings::trackball::TrackballMode mode) {
|
||||
switch (mode) {
|
||||
case settings::trackball::TrackballMode::Encoder: return 0;
|
||||
case settings::trackball::TrackballMode::Pointer: return 1;
|
||||
case LVGL_TRACKBALL_MODE_ENCODER: return 0;
|
||||
case LVGL_TRACKBALL_MODE_POINTER: return 1;
|
||||
}
|
||||
return 0; // default to Encoder
|
||||
}
|
||||
|
||||
class TrackballSettingsApp final : public App {
|
||||
|
||||
settings::trackball::TrackballSettings tbSettings;
|
||||
LvglTrackballSettings tbSettings;
|
||||
bool updated = false;
|
||||
// The trackball indev currently bound by lvgl_devices_attach() at LVGL startup, if any -
|
||||
// there's at most one at a time (see devices.c), so "first active trackball device" reduces
|
||||
// to whatever is already attached.
|
||||
lv_indev_t* trackballIndev = nullptr;
|
||||
lv_obj_t* switchTrackball = nullptr;
|
||||
lv_obj_t* trackballModeDropdown = nullptr;
|
||||
lv_obj_t* encoderSensitivitySlider = nullptr;
|
||||
lv_obj_t* pointerSensitivitySlider = nullptr;
|
||||
|
||||
void applyLive() {
|
||||
if (trackballIndev == nullptr) {
|
||||
return;
|
||||
}
|
||||
lvgl_lock();
|
||||
lvgl_trackball_set_settings(trackballIndev, &tbSettings);
|
||||
if (tbSettings.mode == LVGL_TRACKBALL_MODE_POINTER) {
|
||||
lvgl_trackball_set_cursor_image(trackballIndev, TT_ASSETS_UI_CURSOR);
|
||||
}
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
static void onTrackballSwitch(lv_event_t* e) {
|
||||
auto* app = static_cast<TrackballSettingsApp*>(lv_event_get_user_data(e));
|
||||
bool enabled = lv_obj_has_state(app->switchTrackball, LV_STATE_CHECKED);
|
||||
app->tbSettings.trackballEnabled = enabled;
|
||||
app->tbSettings.enabled = enabled;
|
||||
app->updated = true;
|
||||
trackball::setEnabled(enabled);
|
||||
app->applyLive();
|
||||
|
||||
// Enable/disable controls based on trackball state
|
||||
if (enabled) {
|
||||
@ -72,38 +74,57 @@ class TrackballSettingsApp final : public App {
|
||||
uint32_t selected = lv_dropdown_get_selected(app->trackballModeDropdown);
|
||||
|
||||
// Validate selection matches expected enum values (dropdown order: Encoder=0, Pointer=1)
|
||||
settings::trackball::TrackballMode mode;
|
||||
LvglTrackballMode mode;
|
||||
switch (selected) {
|
||||
case 0: mode = settings::trackball::TrackballMode::Encoder; break;
|
||||
case 1: mode = settings::trackball::TrackballMode::Pointer; break;
|
||||
case 0: mode = LVGL_TRACKBALL_MODE_ENCODER; break;
|
||||
case 1: mode = LVGL_TRACKBALL_MODE_POINTER; break;
|
||||
default: return; // Invalid selection, ignore
|
||||
}
|
||||
|
||||
app->tbSettings.trackballMode = mode;
|
||||
app->tbSettings.mode = mode;
|
||||
app->updated = true;
|
||||
|
||||
// Apply mode change immediately
|
||||
trackball::setMode(toDriverMode(mode));
|
||||
app->applyLive();
|
||||
}
|
||||
|
||||
static void onEncoderSensitivityChanged(lv_event_t* e) {
|
||||
auto* app = static_cast<TrackballSettingsApp*>(lv_event_get_user_data(e));
|
||||
int32_t value = lv_slider_get_value(app->encoderSensitivitySlider);
|
||||
app->tbSettings.encoderSensitivity = static_cast<uint8_t>(value);
|
||||
app->tbSettings.encoder_sensitivity = static_cast<uint8_t>(value);
|
||||
app->updated = true;
|
||||
|
||||
// Apply immediately
|
||||
trackball::setEncoderSensitivity(static_cast<uint8_t>(value));
|
||||
app->applyLive();
|
||||
}
|
||||
|
||||
static void onPointerSensitivityChanged(lv_event_t* e) {
|
||||
auto* app = static_cast<TrackballSettingsApp*>(lv_event_get_user_data(e));
|
||||
int32_t value = lv_slider_get_value(app->pointerSensitivitySlider);
|
||||
app->tbSettings.pointerSensitivity = static_cast<uint8_t>(value);
|
||||
app->tbSettings.pointer_sensitivity = static_cast<uint8_t>(value);
|
||||
app->updated = true;
|
||||
|
||||
// Apply immediately
|
||||
trackball::setPointerSensitivity(static_cast<uint8_t>(value));
|
||||
app->applyLive();
|
||||
}
|
||||
|
||||
static lv_indev_t* findFirstTrackballIndev() {
|
||||
lv_indev_t* indev = lv_indev_get_next(nullptr);
|
||||
while (indev != nullptr) {
|
||||
void* driver_data = lv_indev_get_driver_data(indev);
|
||||
if (driver_data) {
|
||||
LvglDeviceContext* context = static_cast<LvglDeviceContext*>(driver_data);
|
||||
if (context->device) {
|
||||
const DeviceType* device_type = device_get_type(context->device);
|
||||
if (device_type == &TRACKBALL_TYPE) {
|
||||
return indev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indev = lv_indev_get_next(indev);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
@ -111,15 +132,32 @@ public:
|
||||
tbSettings = settings::trackball::loadOrGetDefault();
|
||||
auto ui_density = lvgl_get_ui_density();
|
||||
updated = false;
|
||||
trackballIndev = findFirstTrackballIndev();
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lv_obj_t* toolbar = lvgl::toolbar_create(parent, app);
|
||||
|
||||
if (trackballIndev == nullptr) {
|
||||
auto* wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_width(wrapper, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(wrapper, 1);
|
||||
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(wrapper, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
auto* label = lv_label_create(wrapper);
|
||||
lv_label_set_text(label, "No trackball device found");
|
||||
return;
|
||||
}
|
||||
|
||||
// The live indev may still be running with lvgl_trackball_settings_get_default() (it's
|
||||
// bound at LVGL startup before persisted settings are known) - bring it in line with what
|
||||
// this screen is about to display.
|
||||
applyLive();
|
||||
|
||||
switchTrackball = lvgl_toolbar_add_switch_action(toolbar);
|
||||
lv_obj_add_event_cb(switchTrackball, onTrackballSwitch, LV_EVENT_VALUE_CHANGED, this);
|
||||
if (tbSettings.trackballEnabled) lv_obj_add_state(switchTrackball, LV_STATE_CHECKED);
|
||||
if (tbSettings.enabled) lv_obj_add_state(switchTrackball, LV_STATE_CHECKED);
|
||||
|
||||
auto* main_wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
@ -139,11 +177,11 @@ public:
|
||||
trackballModeDropdown = lv_dropdown_create(tb_mode_wrapper);
|
||||
lv_dropdown_set_options(trackballModeDropdown, "Encoder\nPointer");
|
||||
lv_obj_align(trackballModeDropdown, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
lv_dropdown_set_selected(trackballModeDropdown, modeToDropdownIndex(tbSettings.trackballMode));
|
||||
lv_dropdown_set_selected(trackballModeDropdown, modeToDropdownIndex(tbSettings.mode));
|
||||
lv_obj_add_event_cb(trackballModeDropdown, onTrackballModeChanged, LV_EVENT_VALUE_CHANGED, this);
|
||||
|
||||
// Disable dropdown if trackball is disabled
|
||||
if (!tbSettings.trackballEnabled) {
|
||||
if (!tbSettings.enabled) {
|
||||
lv_obj_add_state(trackballModeDropdown, LV_STATE_DISABLED);
|
||||
}
|
||||
|
||||
@ -162,12 +200,12 @@ public:
|
||||
|
||||
encoderSensitivitySlider = lv_slider_create(enc_sens_wrapper);
|
||||
lv_slider_set_range(encoderSensitivitySlider, 1, 10);
|
||||
lv_slider_set_value(encoderSensitivitySlider, tbSettings.encoderSensitivity, LV_ANIM_OFF);
|
||||
lv_slider_set_value(encoderSensitivitySlider, tbSettings.encoder_sensitivity, LV_ANIM_OFF);
|
||||
lv_obj_set_width(encoderSensitivitySlider, LV_PCT(50));
|
||||
lv_obj_align(encoderSensitivitySlider, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
lv_obj_add_event_cb(encoderSensitivitySlider, onEncoderSensitivityChanged, LV_EVENT_VALUE_CHANGED, this);
|
||||
|
||||
if (!tbSettings.trackballEnabled) {
|
||||
if (!tbSettings.enabled) {
|
||||
lv_obj_add_state(encoderSensitivitySlider, LV_STATE_DISABLED);
|
||||
}
|
||||
|
||||
@ -186,12 +224,12 @@ public:
|
||||
|
||||
pointerSensitivitySlider = lv_slider_create(ptr_sens_wrapper);
|
||||
lv_slider_set_range(pointerSensitivitySlider, 1, 10);
|
||||
lv_slider_set_value(pointerSensitivitySlider, tbSettings.pointerSensitivity, LV_ANIM_OFF);
|
||||
lv_slider_set_value(pointerSensitivitySlider, tbSettings.pointer_sensitivity, LV_ANIM_OFF);
|
||||
lv_obj_set_width(pointerSensitivitySlider, LV_PCT(50));
|
||||
lv_obj_align(pointerSensitivitySlider, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
lv_obj_add_event_cb(pointerSensitivitySlider, onPointerSensitivityChanged, LV_EVENT_VALUE_CHANGED, this);
|
||||
|
||||
if (!tbSettings.trackballEnabled) {
|
||||
if (!tbSettings.enabled) {
|
||||
lv_obj_add_state(pointerSensitivitySlider, LV_STATE_DISABLED);
|
||||
}
|
||||
}
|
||||
@ -214,5 +252,3 @@ extern const AppManifest manifest = {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
45
Tactility/Source/lvgl/TrackballInit.cpp
Normal file
45
Tactility/Source/lvgl/TrackballInit.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include <lvgl/devices/trackball.h>
|
||||
#include <lvgl/devices/device_context.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/trackball.h>
|
||||
|
||||
#include <Tactility/Assets.h>
|
||||
#include <Tactility/settings/TrackballSettings.h>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
static void initTrackball(lv_indev_t* indev, LvglTrackballSettings& settings) {
|
||||
lv_indev_type_t type = lv_indev_get_type(indev);
|
||||
|
||||
void* driver_data = lv_indev_get_driver_data(indev);
|
||||
if (!driver_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
LvglDeviceContext* context = static_cast<LvglDeviceContext*>(driver_data);
|
||||
if (!context->device) {
|
||||
return;
|
||||
}
|
||||
|
||||
const DeviceType* device_type = device_get_type(context->device);
|
||||
if (device_type != &TRACKBALL_TYPE) {
|
||||
return;
|
||||
}
|
||||
|
||||
lvgl_trackball_set_settings(indev, &settings);
|
||||
if (settings.mode == LVGL_TRACKBALL_MODE_POINTER) {
|
||||
lvgl_trackball_set_cursor_image(indev, TT_ASSETS_UI_CURSOR);
|
||||
}
|
||||
}
|
||||
|
||||
void initTrackball() {
|
||||
auto trackball_settings = settings::trackball::loadOrGetDefault();
|
||||
lv_indev_t* indev = lv_indev_get_next(nullptr);
|
||||
while (indev != nullptr) {
|
||||
initTrackball(indev, trackball_settings);
|
||||
indev = lv_indev_get_next(indev);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -23,7 +23,7 @@ constexpr uint8_t MAX_ENCODER_SENSITIVITY = 10;
|
||||
constexpr uint8_t MIN_POINTER_SENSITIVITY = 1;
|
||||
constexpr uint8_t MAX_POINTER_SENSITIVITY = 10;
|
||||
|
||||
bool load(TrackballSettings& settings) {
|
||||
bool load(LvglTrackballSettings& settings) {
|
||||
auto settings_path = getSettingsFilePath();
|
||||
if (!file::isFile(settings_path)) {
|
||||
return false;
|
||||
@ -55,41 +55,37 @@ bool load(TrackballSettings& settings) {
|
||||
auto isTrueValue = [](const std::string& s) {
|
||||
return s == "1" || s == "true" || s == "True" || s == "TRUE";
|
||||
};
|
||||
settings.trackballEnabled = (tb_enabled != map.end()) ? isTrueValue(tb_enabled->second) : true;
|
||||
settings.trackballMode = (tb_mode != map.end() && tb_mode->second == "1") ? TrackballMode::Pointer : TrackballMode::Encoder;
|
||||
settings.encoderSensitivity = (enc_sens != map.end()) ? safeParseUint8(enc_sens->second, MIN_ENCODER_SENSITIVITY) : MIN_ENCODER_SENSITIVITY;
|
||||
settings.pointerSensitivity = (ptr_sens != map.end()) ? safeParseUint8(ptr_sens->second, MAX_POINTER_SENSITIVITY) : MAX_POINTER_SENSITIVITY;
|
||||
settings.enabled = (tb_enabled != map.end()) ? isTrueValue(tb_enabled->second) : true;
|
||||
settings.mode = (tb_mode != map.end() && tb_mode->second == "1") ? LVGL_TRACKBALL_MODE_POINTER : LVGL_TRACKBALL_MODE_ENCODER;
|
||||
auto default_settings = lvgl_trackball_settings_get_default();
|
||||
settings.encoder_sensitivity = (enc_sens != map.end()) ? safeParseUint8(enc_sens->second, default_settings.encoder_sensitivity) : default_settings.encoder_sensitivity;
|
||||
settings.pointer_sensitivity = (ptr_sens != map.end()) ? safeParseUint8(ptr_sens->second, default_settings.pointer_sensitivity) : default_settings.pointer_sensitivity;
|
||||
|
||||
// Clamp values to valid ranges
|
||||
settings.encoderSensitivity = std::clamp(settings.encoderSensitivity, MIN_ENCODER_SENSITIVITY, MAX_ENCODER_SENSITIVITY);
|
||||
settings.pointerSensitivity = std::clamp(settings.pointerSensitivity, MIN_POINTER_SENSITIVITY, MAX_POINTER_SENSITIVITY);
|
||||
settings.encoder_sensitivity = std::clamp(settings.encoder_sensitivity, MIN_ENCODER_SENSITIVITY, MAX_ENCODER_SENSITIVITY);
|
||||
settings.pointer_sensitivity = std::clamp(settings.pointer_sensitivity, MIN_POINTER_SENSITIVITY, MAX_POINTER_SENSITIVITY);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TrackballSettings getDefault() {
|
||||
return TrackballSettings{
|
||||
.trackballEnabled = true,
|
||||
.trackballMode = TrackballMode::Encoder,
|
||||
.encoderSensitivity = MIN_ENCODER_SENSITIVITY,
|
||||
.pointerSensitivity = MAX_POINTER_SENSITIVITY
|
||||
};
|
||||
LvglTrackballSettings getDefault() {
|
||||
return lvgl_trackball_settings_get_default();
|
||||
}
|
||||
|
||||
TrackballSettings loadOrGetDefault() {
|
||||
TrackballSettings s;
|
||||
LvglTrackballSettings loadOrGetDefault() {
|
||||
LvglTrackballSettings s;
|
||||
if (!load(s)) {
|
||||
s = getDefault();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool save(const TrackballSettings& settings) {
|
||||
bool save(const LvglTrackballSettings& settings) {
|
||||
std::map<std::string, std::string> map;
|
||||
map[KEY_TRACKBALL_ENABLED] = settings.trackballEnabled ? "1" : "0";
|
||||
map[KEY_TRACKBALL_MODE] = (settings.trackballMode == TrackballMode::Pointer) ? "1" : "0";
|
||||
map[KEY_ENCODER_SENSITIVITY] = std::to_string(std::clamp(settings.encoderSensitivity, MIN_ENCODER_SENSITIVITY, MAX_ENCODER_SENSITIVITY));
|
||||
map[KEY_POINTER_SENSITIVITY] = std::to_string(std::clamp(settings.pointerSensitivity, MIN_POINTER_SENSITIVITY, MAX_POINTER_SENSITIVITY));
|
||||
map[KEY_TRACKBALL_ENABLED] = settings.enabled ? "1" : "0";
|
||||
map[KEY_TRACKBALL_MODE] = (settings.mode == LVGL_TRACKBALL_MODE_POINTER) ? "1" : "0";
|
||||
map[KEY_ENCODER_SENSITIVITY] = std::to_string(std::clamp(settings.encoder_sensitivity, MIN_ENCODER_SENSITIVITY, MAX_ENCODER_SENSITIVITY));
|
||||
map[KEY_POINTER_SENSITIVITY] = std::to_string(std::clamp(settings.pointer_sensitivity, MIN_POINTER_SENSITIVITY, MAX_POINTER_SENSITIVITY));
|
||||
auto settings_path = getSettingsFilePath();
|
||||
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
|
||||
return false;
|
||||
|
||||
@ -5,28 +5,18 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <tactility/drivers/gpio.h>
|
||||
#include <tactility/error.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct Device;
|
||||
struct DeviceType;
|
||||
|
||||
struct TdeckTrackballConfig {
|
||||
struct GpioPinSpec pin_right;
|
||||
struct GpioPinSpec pin_up;
|
||||
struct GpioPinSpec pin_left;
|
||||
struct GpioPinSpec pin_down;
|
||||
struct GpioPinSpec pin_click;
|
||||
};
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
/**
|
||||
* @brief API for the T-Deck 5-way trackball driver.
|
||||
* @brief API for trackball drivers.
|
||||
* Reports raw, unscaled movement: sensitivity and mode (encoder vs. pointer) are UI concerns
|
||||
* layered on top by the consumer, not something this driver knows about.
|
||||
* layered on top by the consumer (see lvgl/devices/trackball.h), not something this driver knows about.
|
||||
*/
|
||||
struct TdeckTrackballApi {
|
||||
struct TrackballApi {
|
||||
/**
|
||||
* @brief Reads the accumulated movement since the last read, then resets it to zero.
|
||||
* @param[in] device the trackball device
|
||||
@ -48,14 +38,14 @@ struct TdeckTrackballApi {
|
||||
/**
|
||||
* @brief Reads the accumulated movement using the specified trackball device.
|
||||
*/
|
||||
error_t tdeck_trackball_read_delta(struct Device* device, int32_t* out_dx, int32_t* out_dy);
|
||||
error_t trackball_read_delta(struct Device* device, int32_t* out_dx, int32_t* out_dy);
|
||||
|
||||
/**
|
||||
* @brief Gets whether the click button is currently pressed on the specified trackball device.
|
||||
*/
|
||||
error_t tdeck_trackball_get_button_pressed(struct Device* device, bool* out_pressed);
|
||||
error_t trackball_get_button_pressed(struct Device* device, bool* out_pressed);
|
||||
|
||||
extern const struct DeviceType TDECK_TRACKBALL_TYPE;
|
||||
extern const struct DeviceType TRACKBALL_TYPE;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
24
TactilityKernel/source/drivers/trackball.cpp
Normal file
24
TactilityKernel/source/drivers/trackball.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/trackball.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
#define TRACKBALL_DRIVER_API(driver) ((struct TrackballApi*)driver->api)
|
||||
|
||||
extern "C" {
|
||||
|
||||
error_t trackball_read_delta(Device* device, int32_t* out_dx, int32_t* out_dy) {
|
||||
const auto* driver = device_get_driver(device);
|
||||
return TRACKBALL_DRIVER_API(driver)->read_delta(device, out_dx, out_dy);
|
||||
}
|
||||
|
||||
error_t trackball_get_button_pressed(Device* device, bool* out_pressed) {
|
||||
const auto* driver = device_get_driver(device);
|
||||
return TRACKBALL_DRIVER_API(driver)->get_button_pressed(device, out_pressed);
|
||||
}
|
||||
|
||||
const DeviceType TRACKBALL_TYPE {
|
||||
.name = "trackball"
|
||||
};
|
||||
|
||||
}
|
||||
@ -29,6 +29,7 @@
|
||||
#include <tactility/drivers/rtc.h>
|
||||
#include <tactility/drivers/sdcard.h>
|
||||
#include <tactility/drivers/spi_controller.h>
|
||||
#include <tactility/drivers/trackball.h>
|
||||
#include <tactility/drivers/uart_controller.h>
|
||||
#include <tactility/drivers/usb_host_hid.h>
|
||||
#include <tactility/drivers/usb_host_midi.h>
|
||||
@ -294,6 +295,10 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
||||
DEFINE_MODULE_SYMBOL(spi_controller_try_lock),
|
||||
DEFINE_MODULE_SYMBOL(spi_controller_unlock),
|
||||
DEFINE_MODULE_SYMBOL(SPI_CONTROLLER_TYPE),
|
||||
// drivers/trackball
|
||||
DEFINE_MODULE_SYMBOL(trackball_read_delta),
|
||||
DEFINE_MODULE_SYMBOL(trackball_get_button_pressed),
|
||||
DEFINE_MODULE_SYMBOL(TRACKBALL_TYPE),
|
||||
// drivers/uart_controller
|
||||
DEFINE_MODULE_SYMBOL(uart_controller_open),
|
||||
DEFINE_MODULE_SYMBOL(uart_controller_close),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user