Migrate TactilityC and LVGL functionality (#588)
lvgl-module - Move and rename source and include files - Fix bug with missing lvgl unlock - New widgets: spinner, toolbar, sliderbox TactilityC - Removed tt_lock, tt_lvgl_\*
This commit is contained in:
committed by
GitHub
parent
cd2d9d6158
commit
b98a813f3c
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Create a SliderBox: a slider flanked by "-" and "+" buttons with a value label.
|
||||
* @param[in] parent the parent object
|
||||
* @param[in] min minimum value (inclusive)
|
||||
* @param[in] max maximum value (inclusive)
|
||||
* @param[in] step the amount each +/- button press changes the value by
|
||||
* @param[in] value the initial value
|
||||
* @return the created SliderBox instance
|
||||
*/
|
||||
lv_obj_t* lvgl_sliderbox_create(lv_obj_t* parent, int32_t min, int32_t max, int32_t step, int32_t value);
|
||||
|
||||
/** @return the current value of the SliderBox */
|
||||
int32_t lvgl_sliderbox_get_value(lv_obj_t* obj);
|
||||
|
||||
/**
|
||||
* Set the current value of the SliderBox (clamped to its [min, max] range).
|
||||
* Updates the slider position and the value label.
|
||||
*/
|
||||
void lvgl_sliderbox_set_value(lv_obj_t* obj, int32_t value, lv_anim_enable_t anim);
|
||||
|
||||
/**
|
||||
* Register a callback that is fired with LV_EVENT_VALUE_CHANGED whenever the value
|
||||
* changes, whether the change came from the slider or from the +/- buttons.
|
||||
* @param[in] obj the SliderBox instance
|
||||
* @param[in] callback the callback to invoke
|
||||
* @param[in] userData user data that is attached to the event
|
||||
*/
|
||||
void lvgl_sliderbox_add_value_changed_cb(lv_obj_t* obj, lv_event_cb_t callback, void* userData);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Creates the Tactility spinner widget.
|
||||
* @param[in] parent the parent object for the new spinner
|
||||
* @return the created spinner
|
||||
*/
|
||||
lv_obj_t* lvgl_spinner_create(lv_obj_t* parent);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define TOOLBAR_ACTION_LIMIT 4
|
||||
|
||||
/** Configuration for toolbar-wide defaults */
|
||||
typedef struct {
|
||||
/** Callback invoked when a toolbar's default navigation (top-left) button is pressed */
|
||||
lv_event_cb_t nav_action_callback;
|
||||
} ToolbarConfig;
|
||||
|
||||
/**
|
||||
* @brief Configure toolbar-wide defaults. Must be called before any toolbar is created (e.g. during LVGL startup).
|
||||
* @param[in] config the configuration to apply
|
||||
*/
|
||||
void lvgl_toolbar_configure(const ToolbarConfig* config);
|
||||
|
||||
/**
|
||||
* @brief Create a toolbar widget with the given title.
|
||||
* @param[in] parent the parent object for the new toolbar
|
||||
* @param[in] title the toolbar title
|
||||
* @return the created toolbar
|
||||
*/
|
||||
lv_obj_t* lvgl_toolbar_create(lv_obj_t* parent, const char* title);
|
||||
|
||||
/** Sets the toolbar title */
|
||||
void lvgl_toolbar_set_title(lv_obj_t* obj, const char* title);
|
||||
|
||||
/**
|
||||
* Sets the navigation action of the toolbar (button on the top-left)
|
||||
* @param[in] obj the toolbar instance
|
||||
* @param[in] icon the icon to set on the button
|
||||
* @param[in] callback the callback for the click action of the button
|
||||
* @param[in] userData the user data that is attached to the callback event object
|
||||
*/
|
||||
void lvgl_toolbar_set_nav_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* userData);
|
||||
|
||||
/**
|
||||
* Create and add an action button with an image to the toolbar (aligned to the right of the toolbar)
|
||||
* @param[in] obj the toolbar instance
|
||||
* @param[in] imagePath the path to an image file to shown on the button
|
||||
* @param[in] callback the callback for the click action of the button
|
||||
* @param[in] callbackUserData the user data that is passed to the callback
|
||||
* @return an lv_button instance
|
||||
*/
|
||||
lv_obj_t* lvgl_toolbar_add_image_button_action(lv_obj_t* obj, const char* imagePath, lv_event_cb_t callback, void* callbackUserData);
|
||||
|
||||
/**
|
||||
* Create and add an action button with text to the toolbar (aligned to the right of the toolbar)
|
||||
* @param[in] obj the toolbar instance
|
||||
* @param[in] text the button text
|
||||
* @param[in] callback the callback for the click action of the button
|
||||
* @param[in] callbackUserData the user data that is passed to the callback
|
||||
* @return an lv_button instance
|
||||
*/
|
||||
lv_obj_t* lvgl_toolbar_add_text_button_action(lv_obj_t* obj, const char* text, lv_event_cb_t callback, void* callbackUserData);
|
||||
|
||||
/**
|
||||
* Create and add a switch to the toolbar actions.
|
||||
* @param[in] obj the toolbar instance
|
||||
* @return an instance created by lv_switch_create()
|
||||
*/
|
||||
lv_obj_t* lvgl_toolbar_add_switch_action(lv_obj_t* obj);
|
||||
|
||||
/**
|
||||
* Create and add a spinner to the toolbar actions.
|
||||
* @param[in] obj the toolbar instance
|
||||
* @return an instance created by lvgl_spinner_create()
|
||||
*/
|
||||
lv_obj_t* lvgl_toolbar_add_spinner_action(lv_obj_t* obj);
|
||||
|
||||
/**
|
||||
* Remove all actions from the toolbar
|
||||
* @param[in] obj the toolbar instance
|
||||
*/
|
||||
void lvgl_toolbar_clear_actions(lv_obj_t* obj);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
#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/lvgl_display.h>
|
||||
#include <lvgl/lvgl_keyboard.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <lvgl/lvgl_pointer.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
@@ -115,5 +115,5 @@ void lvgl_devices_detach() {
|
||||
display = lv_disp_get_next(NULL);
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
lvgl_unlock();
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/lvgl_display.h>
|
||||
#include <lvgl/devices/display.h>
|
||||
|
||||
#include <lvgl/lvgl_ppa.h>
|
||||
#include <lvgl/ppa.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/lvgl_keyboard.h>
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/lvgl_pointer.h>
|
||||
#include <lvgl/devices/pointer.h>
|
||||
|
||||
#include <tactility/drivers/pointer.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl.h>
|
||||
#include <lvgl/lvgl_fonts.h>
|
||||
#include <lvgl/fonts.h>
|
||||
#include <tactility/check.h>
|
||||
|
||||
// The preprocessor definitions that are used below are defined in the CMakeLists.txt from this module.
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/lvgl_ppa.h>
|
||||
#include <lvgl/ppa.h>
|
||||
|
||||
#include <tactility/log.h>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/fonts.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <lvgl/module.h>
|
||||
#include <lvgl/lvgl_fonts.h>
|
||||
|
||||
#include <lvgl/devices/display.h>
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
#include <lvgl/devices/pointer.h>
|
||||
|
||||
#include <lvgl/widgets/sliderbox.h>
|
||||
#include <lvgl/widgets/spinner.h>
|
||||
#include <lvgl/widgets/toolbar.h>
|
||||
|
||||
#include <tactility/module.h>
|
||||
|
||||
@@ -26,6 +34,35 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
||||
DEFINE_MODULE_SYMBOL(lvgl_get_launcher_icon_font_height),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_get_statusbar_icon_font),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_get_statusbar_icon_font_height),
|
||||
// lvgl_display
|
||||
DEFINE_MODULE_SYMBOL(lvgl_display_add),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_display_remove),
|
||||
// lvgl_keyboard
|
||||
DEFINE_MODULE_SYMBOL(lvgl_keyboard_add),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_keyboard_remove),
|
||||
// lvgl_pointer
|
||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_set_calibration),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_get_calibration),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_get_default),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_add),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_remove),
|
||||
// lvgl_spinner
|
||||
DEFINE_MODULE_SYMBOL(lvgl_spinner_create),
|
||||
// lvgl_toolbar
|
||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_configure),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_create),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_set_title),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_set_nav_action),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_image_button_action),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_text_button_action),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_switch_action),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_spinner_action),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_clear_actions),
|
||||
// lvgl_sliderbox
|
||||
DEFINE_MODULE_SYMBOL(lvgl_sliderbox_create),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_sliderbox_get_value),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_sliderbox_set_value),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_sliderbox_add_value_changed_cb),
|
||||
// lv_event
|
||||
DEFINE_MODULE_SYMBOL(lv_event_get_code),
|
||||
DEFINE_MODULE_SYMBOL(lv_event_get_indev),
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
|
||||
|
||||
#include <lvgl/widgets/sliderbox.h>
|
||||
#include <lvgl/fonts.h>
|
||||
|
||||
typedef struct {
|
||||
lv_obj_t obj;
|
||||
lv_obj_t* minusButton;
|
||||
lv_obj_t* slider;
|
||||
lv_obj_t* valueLabel;
|
||||
lv_obj_t* plusButton;
|
||||
int32_t min;
|
||||
int32_t max;
|
||||
int32_t step;
|
||||
} SliderBox;
|
||||
|
||||
static void sliderbox_constructor(const lv_obj_class_t* classPointer, lv_obj_t* obj);
|
||||
|
||||
static lv_obj_class_t sliderbox_class = {
|
||||
.base_class = &lv_obj_class,
|
||||
.constructor_cb = &sliderbox_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.name = "tt_sliderbox",
|
||||
.width_def = LV_PCT(100),
|
||||
.height_def = LV_SIZE_CONTENT,
|
||||
.editable = false,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
|
||||
.instance_size = sizeof(SliderBox),
|
||||
.theme_inheritable = false
|
||||
};
|
||||
|
||||
static void sliderbox_constructor(const lv_obj_class_t* classPointer, lv_obj_t* obj) {
|
||||
LV_UNUSED(classPointer);
|
||||
lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_all(obj, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_pad_column(obj, 4, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_border_width(obj, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_bg_opa(obj, 0, LV_STATE_DEFAULT);
|
||||
}
|
||||
|
||||
static void update_value_label(SliderBox* sliderBox) {
|
||||
auto value = lv_slider_get_value(sliderBox->slider);
|
||||
lv_label_set_text_fmt(sliderBox->valueLabel, "%" LV_PRId32, value);
|
||||
}
|
||||
|
||||
static void update_button_states(SliderBox* sliderBox) {
|
||||
auto value = lv_slider_get_value(sliderBox->slider);
|
||||
|
||||
if (value <= sliderBox->min) {
|
||||
lv_obj_add_state(sliderBox->minusButton, LV_STATE_DISABLED);
|
||||
} else {
|
||||
lv_obj_remove_state(sliderBox->minusButton, LV_STATE_DISABLED);
|
||||
}
|
||||
|
||||
if (value >= sliderBox->max) {
|
||||
lv_obj_add_state(sliderBox->plusButton, LV_STATE_DISABLED);
|
||||
} else {
|
||||
lv_obj_remove_state(sliderBox->plusButton, LV_STATE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
static void notify_value_changed(lv_obj_t* obj) {
|
||||
lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
}
|
||||
|
||||
static void on_slider_value_changed(lv_event_t* event) {
|
||||
auto* obj = static_cast<lv_obj_t*>(lv_event_get_user_data(event));
|
||||
auto* sliderBox = reinterpret_cast<SliderBox*>(obj);
|
||||
|
||||
auto raw_value = lv_slider_get_value(sliderBox->slider);
|
||||
auto steps_from_min = (raw_value - sliderBox->min + sliderBox->step / 2) / sliderBox->step;
|
||||
auto snapped_value = sliderBox->min + steps_from_min * sliderBox->step;
|
||||
if (snapped_value > sliderBox->max) {
|
||||
snapped_value = sliderBox->max;
|
||||
}
|
||||
|
||||
if (snapped_value != raw_value) {
|
||||
lv_slider_set_value(sliderBox->slider, snapped_value, LV_ANIM_OFF);
|
||||
}
|
||||
|
||||
update_value_label(sliderBox);
|
||||
update_button_states(sliderBox);
|
||||
notify_value_changed(obj);
|
||||
}
|
||||
|
||||
static void on_step_button_clicked(lv_event_t* event, int32_t direction) {
|
||||
auto* obj = static_cast<lv_obj_t*>(lv_event_get_user_data(event));
|
||||
auto* sliderBox = reinterpret_cast<SliderBox*>(obj);
|
||||
|
||||
auto value = lv_slider_get_value(sliderBox->slider);
|
||||
auto new_value = value + direction * sliderBox->step;
|
||||
if (new_value < sliderBox->min) {
|
||||
new_value = sliderBox->min;
|
||||
} else if (new_value > sliderBox->max) {
|
||||
new_value = sliderBox->max;
|
||||
}
|
||||
|
||||
if (new_value != value) {
|
||||
lv_slider_set_value(sliderBox->slider, new_value, LV_ANIM_ON);
|
||||
update_value_label(sliderBox);
|
||||
notify_value_changed(obj);
|
||||
}
|
||||
|
||||
update_button_states(sliderBox);
|
||||
}
|
||||
|
||||
static void on_minus_button_clicked(lv_event_t* event) {
|
||||
on_step_button_clicked(event, -1);
|
||||
}
|
||||
|
||||
static void on_plus_button_clicked(lv_event_t* event) {
|
||||
on_step_button_clicked(event, 1);
|
||||
}
|
||||
|
||||
static lv_obj_t* create_step_button(lv_obj_t* parent, const char* symbol, lv_event_cb_t callback, void* userData, uint32_t size) {
|
||||
auto* button = lv_button_create(parent);
|
||||
lv_obj_remove_flag(button, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_size(button, size, size);
|
||||
lv_obj_set_style_pad_all(button, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_add_event_cb(button, callback, LV_EVENT_SHORT_CLICKED, userData);
|
||||
|
||||
auto* label = lv_label_create(button);
|
||||
lv_label_set_text(label, symbol);
|
||||
lv_obj_center(label);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
lv_obj_t* lvgl_sliderbox_create(lv_obj_t* parent, int32_t min, int32_t max, int32_t step, int32_t value) {
|
||||
if (max <= min) {
|
||||
max = min + 1;
|
||||
}
|
||||
if (step <= 0) {
|
||||
step = 1;
|
||||
}
|
||||
|
||||
lv_obj_t* obj = lv_obj_class_create_obj(&sliderbox_class, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
|
||||
auto* sliderBox = reinterpret_cast<SliderBox*>(obj);
|
||||
sliderBox->min = min;
|
||||
sliderBox->max = max;
|
||||
sliderBox->step = step;
|
||||
|
||||
auto button_size = lvgl_get_text_font_height(FONT_SIZE_DEFAULT) + 8;
|
||||
|
||||
sliderBox->minusButton = create_step_button(obj, LV_SYMBOL_MINUS, &on_minus_button_clicked, obj, button_size);
|
||||
|
||||
sliderBox->slider = lv_slider_create(obj);
|
||||
lv_obj_remove_flag(sliderBox->slider, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_flex_grow(sliderBox->slider, 1);
|
||||
lv_slider_set_range(sliderBox->slider, min, max);
|
||||
lv_obj_add_event_cb(sliderBox->slider, &on_slider_value_changed, LV_EVENT_VALUE_CHANGED, obj);
|
||||
|
||||
sliderBox->valueLabel = lv_label_create(obj);
|
||||
lv_obj_set_style_text_align(sliderBox->valueLabel, LV_TEXT_ALIGN_RIGHT, LV_STATE_DEFAULT);
|
||||
lv_label_set_long_mode(sliderBox->valueLabel, LV_LABEL_LONG_MODE_CLIP);
|
||||
|
||||
// Reserve enough width for the largest value so the layout doesn't jump around.
|
||||
// +4 padding wasn't enough margin for 3-digit values to never wrap; widened to
|
||||
// a flat per-character estimate instead of trusting raw glyph width too tightly.
|
||||
char buffer[16];
|
||||
lv_snprintf(buffer, sizeof(buffer), "%" LV_PRId32, min);
|
||||
auto width_min = lv_text_get_width(buffer, lv_strlen(buffer), lv_obj_get_style_text_font(sliderBox->valueLabel, LV_PART_MAIN), 0);
|
||||
lv_snprintf(buffer, sizeof(buffer), "%" LV_PRId32, max);
|
||||
auto width_max = lv_text_get_width(buffer, lv_strlen(buffer), lv_obj_get_style_text_font(sliderBox->valueLabel, LV_PART_MAIN), 0);
|
||||
auto max_width = (width_min > width_max) ? width_min : width_max;
|
||||
lv_obj_set_width(sliderBox->valueLabel, max_width + 8);
|
||||
|
||||
sliderBox->plusButton = create_step_button(obj, LV_SYMBOL_PLUS, &on_plus_button_clicked, obj, button_size);
|
||||
|
||||
lvgl_sliderbox_set_value(obj, value, LV_ANIM_OFF);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
int32_t lvgl_sliderbox_get_value(lv_obj_t* obj) {
|
||||
auto* sliderBox = reinterpret_cast<SliderBox*>(obj);
|
||||
return lv_slider_get_value(sliderBox->slider);
|
||||
}
|
||||
|
||||
void lvgl_sliderbox_set_value(lv_obj_t* obj, int32_t value, lv_anim_enable_t anim) {
|
||||
auto* sliderBox = reinterpret_cast<SliderBox*>(obj);
|
||||
|
||||
if (value < sliderBox->min) {
|
||||
value = sliderBox->min;
|
||||
} else if (value > sliderBox->max) {
|
||||
value = sliderBox->max;
|
||||
}
|
||||
|
||||
lv_slider_set_value(sliderBox->slider, value, anim);
|
||||
update_value_label(sliderBox);
|
||||
update_button_states(sliderBox);
|
||||
}
|
||||
|
||||
void lvgl_sliderbox_add_value_changed_cb(lv_obj_t* obj, lv_event_cb_t callback, void* userData) {
|
||||
lv_obj_add_event_cb(obj, callback, LV_EVENT_VALUE_CHANGED, userData);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
|
||||
|
||||
#include <lvgl/widgets/spinner.h>
|
||||
|
||||
// TODO: Don't depend on asset from a different project
|
||||
#define SPINNER_ASSET "A:/system/spinner.png"
|
||||
|
||||
static void spinner_constructor(const lv_obj_class_t* object_class, lv_obj_t* object);
|
||||
|
||||
static const lv_obj_class_t tt_spinner_class = {
|
||||
.base_class = &lv_image_class,
|
||||
.constructor_cb = spinner_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.name = "tt_spinner",
|
||||
.width_def = 0,
|
||||
.height_def = 0,
|
||||
.editable = 0,
|
||||
.group_def = 0,
|
||||
.instance_size = 0,
|
||||
.theme_inheritable = 0
|
||||
};
|
||||
|
||||
lv_obj_t* lvgl_spinner_create(lv_obj_t* parent) {
|
||||
lv_obj_t* obj = lv_obj_class_create_obj(&tt_spinner_class, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
|
||||
lv_image_set_src(obj, SPINNER_ASSET);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void anim_rotation_callback(void* var, int32_t v) {
|
||||
auto* object = (lv_obj_t*) var;
|
||||
auto width = lv_obj_get_width(object);
|
||||
auto height = lv_obj_get_width(object);
|
||||
lv_obj_set_style_transform_pivot_x(object, width / 2, 0);
|
||||
lv_obj_set_style_transform_pivot_y(object, height / 2, 0);
|
||||
lv_obj_set_style_transform_rotation(object, v, 0);
|
||||
}
|
||||
|
||||
static void spinner_constructor(const lv_obj_class_t* object_class, lv_obj_t* object) {
|
||||
lv_obj_remove_flag(object, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
lv_anim_t a;
|
||||
lv_anim_init(&a);
|
||||
lv_anim_set_var(&a, object);
|
||||
lv_anim_set_values(&a, 0, 3600);
|
||||
lv_anim_set_duration(&a, 800);
|
||||
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
|
||||
lv_anim_set_exec_cb(&a, anim_rotation_callback);
|
||||
lv_anim_start(&a);
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
|
||||
|
||||
#include <lvgl/widgets/toolbar.h>
|
||||
#include <lvgl/widgets/spinner.h>
|
||||
#include <lvgl/fonts.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
|
||||
static uint32_t getToolbarHeight(UiDensity uiDensity) {
|
||||
if (uiDensity == LVGL_UI_DENSITY_COMPACT) {
|
||||
return lvgl_get_text_font_height(FONT_SIZE_DEFAULT) * 1.4f;
|
||||
} else {
|
||||
return lvgl_get_text_font_height(FONT_SIZE_LARGE) * 2.2f;
|
||||
}
|
||||
}
|
||||
|
||||
static const _lv_font_t* getToolbarFont(UiDensity uiDensity) {
|
||||
if (uiDensity == LVGL_UI_DENSITY_COMPACT) {
|
||||
return lvgl_get_text_font(FONT_SIZE_DEFAULT);
|
||||
} else {
|
||||
return lvgl_get_text_font(FONT_SIZE_LARGE);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t getActionIconPadding(UiDensity uiDensity) {
|
||||
auto toolbar_height = getToolbarHeight(uiDensity);
|
||||
// Minimal 8 pixels total padding for selection/animation (4+4 pixels)
|
||||
return (uiDensity != LVGL_UI_DENSITY_COMPACT) ? (uint32_t)(toolbar_height * 0.2f) : 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps with button expansion and also with vertical alignment of content,
|
||||
* as the parent flex doesn't allow for vertical alignment
|
||||
*/
|
||||
static lv_obj_t* create_action_wrapper(lv_obj_t* parent, UiDensity ui_density) {
|
||||
auto* wrapper = lv_obj_create(parent);
|
||||
auto toolbar_height = getToolbarHeight(ui_density);
|
||||
lv_obj_set_size(wrapper, LV_SIZE_CONTENT, toolbar_height);
|
||||
|
||||
auto icon_padding = getActionIconPadding(ui_density);
|
||||
auto icon_padding_half = icon_padding / 2;
|
||||
|
||||
lv_obj_set_style_pad_all(wrapper, icon_padding_half, LV_STATE_DEFAULT); // For selection and touch animation
|
||||
lv_obj_set_style_bg_opa(wrapper, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_border_width(wrapper, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_border_opa(wrapper, 0, LV_STATE_DEFAULT);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
lv_obj_t obj;
|
||||
lv_obj_t* title_label;
|
||||
lv_obj_t* close_button;
|
||||
lv_obj_t* close_button_image;
|
||||
lv_obj_t* action_container;
|
||||
uint8_t action_count;
|
||||
} Toolbar;
|
||||
|
||||
static void toolbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
|
||||
|
||||
static lv_obj_class_t toolbar_class = {
|
||||
.base_class = &lv_obj_class,
|
||||
.constructor_cb = &toolbar_constructor,
|
||||
.destructor_cb = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.name = nullptr,
|
||||
.width_def = LV_PCT(100),
|
||||
.height_def = 0,
|
||||
.editable = false,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
|
||||
.instance_size = sizeof(Toolbar),
|
||||
.theme_inheritable = false
|
||||
};
|
||||
|
||||
static lv_event_cb_t nav_action_callback = nullptr;
|
||||
|
||||
void lvgl_toolbar_configure(const ToolbarConfig* config) {
|
||||
nav_action_callback = config->nav_action_callback;
|
||||
}
|
||||
|
||||
static void default_nav_action(lv_event_t* event) {
|
||||
if (nav_action_callback != nullptr) {
|
||||
nav_action_callback(event);
|
||||
}
|
||||
}
|
||||
|
||||
static void toolbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) {
|
||||
LV_UNUSED(class_p);
|
||||
lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
|
||||
}
|
||||
|
||||
lv_obj_t* lvgl_toolbar_create(lv_obj_t* parent, const char* title) {
|
||||
auto ui_density = lvgl_get_ui_density();
|
||||
auto toolbar_height = getToolbarHeight(ui_density);
|
||||
toolbar_class.height_def = toolbar_height;
|
||||
lv_obj_t* obj = lv_obj_class_create_obj(&toolbar_class, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
lv_obj_set_height(obj, toolbar_height);
|
||||
|
||||
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
||||
lv_obj_set_width(obj, LV_PCT(100));
|
||||
lv_obj_set_style_pad_all(obj, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_pad_column(obj, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lv_obj_center(obj);
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
|
||||
|
||||
auto icon_padding = getActionIconPadding(ui_density);
|
||||
|
||||
auto* close_button_wrapper = create_action_wrapper(obj, ui_density);
|
||||
|
||||
toolbar->close_button = lv_button_create(close_button_wrapper);
|
||||
if (ui_density == LVGL_UI_DENSITY_COMPACT) {
|
||||
lv_obj_set_style_bg_opa(toolbar->close_button, LV_OPA_TRANSP, LV_STATE_DEFAULT);
|
||||
}
|
||||
|
||||
lv_obj_set_size(toolbar->close_button, toolbar_height - icon_padding, toolbar_height - icon_padding);
|
||||
|
||||
lv_obj_set_style_pad_all(toolbar->close_button, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_align(toolbar->close_button, LV_ALIGN_CENTER, 0, 0);
|
||||
toolbar->close_button_image = lv_image_create(toolbar->close_button);
|
||||
lv_obj_align(toolbar->close_button_image, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
auto* title_wrapper = lv_obj_create(obj);
|
||||
uint32_t title_left_padding = (ui_density != LVGL_UI_DENSITY_COMPACT) ? icon_padding : 2;
|
||||
uint32_t title_right_padding = (ui_density != LVGL_UI_DENSITY_COMPACT) ? (icon_padding / 2) : 2;
|
||||
lv_obj_set_size(title_wrapper, LV_SIZE_CONTENT, LV_PCT(100));
|
||||
lv_obj_set_style_bg_opa(title_wrapper, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_pad_left(title_wrapper, title_left_padding, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_pad_right(title_wrapper, title_right_padding, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_pad_ver(title_wrapper, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_border_width(title_wrapper, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_flex_grow(title_wrapper, 1);
|
||||
|
||||
toolbar->title_label = lv_label_create(title_wrapper);
|
||||
lv_obj_set_style_text_font(toolbar->title_label, getToolbarFont(ui_density), LV_STATE_DEFAULT);
|
||||
lv_label_set_text(toolbar->title_label, title);
|
||||
lv_label_set_long_mode(toolbar->title_label, LV_LABEL_LONG_MODE_SCROLL);
|
||||
lv_obj_set_style_text_align(toolbar->title_label, LV_TEXT_ALIGN_LEFT, LV_STATE_DEFAULT);
|
||||
lv_obj_align(toolbar->title_label, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
lv_obj_set_width(toolbar->title_label, LV_PCT(100));
|
||||
|
||||
toolbar->action_container = lv_obj_create(obj);
|
||||
lv_obj_set_width(toolbar->action_container, LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_flow(toolbar->action_container, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_style_pad_all(toolbar->action_container, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_pad_column(toolbar->action_container, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_border_width(toolbar->action_container, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_bg_opa(toolbar->action_container, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl_toolbar_set_nav_action(obj, LV_SYMBOL_CLOSE, &default_nav_action, nullptr);
|
||||
|
||||
// If we don't have a touch device, we assume there's some other kind of input like a keyboard, an encoder or button control
|
||||
// In that scenario we want to automatically have the close button selected so the user doesn't have to press the widget selection
|
||||
// an extra time for every screen.
|
||||
if (!device_has_active_by_type(&POINTER_TYPE)) {
|
||||
lv_group_focus_obj(toolbar->close_button);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void lvgl_toolbar_set_title(lv_obj_t* obj, const char* title) {
|
||||
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
||||
lv_label_set_text(toolbar->title_label, title);
|
||||
}
|
||||
|
||||
void lvgl_toolbar_set_nav_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data) {
|
||||
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
||||
lv_obj_add_event_cb(toolbar->close_button, callback, LV_EVENT_SHORT_CLICKED, user_data);
|
||||
lv_image_set_src(toolbar->close_button_image, icon); // e.g. LV_SYMBOL_CLOSE
|
||||
}
|
||||
|
||||
static lv_obj_t* toolbar_add_button_action(lv_obj_t* obj, const char* imageOrButton, bool isImage, lv_event_cb_t callback, void* user_data) {
|
||||
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
||||
check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached");
|
||||
toolbar->action_count++;
|
||||
|
||||
auto ui_density = lvgl_get_ui_density();
|
||||
auto toolbar_height = getToolbarHeight(ui_density);
|
||||
|
||||
auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density);
|
||||
|
||||
auto padding = getActionIconPadding(ui_density);
|
||||
|
||||
lv_obj_t* action_button = lv_button_create(wrapper);
|
||||
lv_obj_set_size(action_button, toolbar_height - padding, toolbar_height - padding);
|
||||
lv_obj_set_style_pad_all(action_button, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_align(action_button, LV_ALIGN_CENTER, 0, 0);
|
||||
if (ui_density == LVGL_UI_DENSITY_COMPACT) {
|
||||
lv_obj_set_style_bg_opa(action_button, LV_OPA_TRANSP, LV_STATE_DEFAULT);
|
||||
}
|
||||
|
||||
lv_obj_add_event_cb(action_button, callback, LV_EVENT_SHORT_CLICKED, user_data);
|
||||
lv_obj_t* button_content;
|
||||
if (isImage) {
|
||||
button_content = lv_image_create(action_button);
|
||||
lv_image_set_src(button_content, imageOrButton);
|
||||
} else {
|
||||
button_content = lv_label_create(action_button);
|
||||
lv_label_set_text(button_content, imageOrButton);
|
||||
}
|
||||
lv_obj_align(button_content, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
return action_button;
|
||||
}
|
||||
|
||||
lv_obj_t* lvgl_toolbar_add_image_button_action(lv_obj_t* obj, const char* imagePath, lv_event_cb_t callback, void* user_data) {
|
||||
return toolbar_add_button_action(obj, imagePath, true, callback, user_data);
|
||||
}
|
||||
|
||||
lv_obj_t* lvgl_toolbar_add_text_button_action(lv_obj_t* obj, const char* text, lv_event_cb_t callback, void* user_data) {
|
||||
return toolbar_add_button_action(obj, text, false, callback, user_data);
|
||||
}
|
||||
|
||||
lv_obj_t* lvgl_toolbar_add_switch_action(lv_obj_t* obj) {
|
||||
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
||||
check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached");
|
||||
toolbar->action_count++;
|
||||
|
||||
auto ui_density = lvgl_get_ui_density();
|
||||
auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density);
|
||||
lv_obj_set_style_pad_hor(wrapper, 4, LV_STATE_DEFAULT);
|
||||
|
||||
lv_obj_t* widget = lv_switch_create(wrapper);
|
||||
lv_obj_set_align(widget, LV_ALIGN_CENTER);
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
lv_obj_t* lvgl_toolbar_add_spinner_action(lv_obj_t* obj) {
|
||||
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
||||
check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached");
|
||||
toolbar->action_count++;
|
||||
|
||||
auto ui_density = lvgl_get_ui_density();
|
||||
auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density);
|
||||
|
||||
auto* spinner = lvgl_spinner_create(wrapper);
|
||||
lv_obj_set_align(spinner, LV_ALIGN_CENTER);
|
||||
|
||||
if (lv_display_get_color_format(lv_obj_get_display(obj)) == LV_COLOR_FORMAT_L8) {
|
||||
lv_obj_set_style_image_recolor(spinner, lv_theme_get_color_secondary(obj), LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_image_recolor_opa(spinner, LV_OPA_COVER, LV_STATE_DEFAULT);
|
||||
}
|
||||
|
||||
return spinner;
|
||||
}
|
||||
|
||||
void lvgl_toolbar_clear_actions(lv_obj_t* obj) {
|
||||
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
||||
lv_obj_clean(toolbar->action_container);
|
||||
toolbar->action_count = 0;
|
||||
}
|
||||
Reference in New Issue
Block a user