What do you want to achieve?
A radial menu like you’d see in a video game.
What have you tried so far?
I have modified the Pie Chart with clickable slices using Arcs from examples.
The problem I’m having is the accuracy for events like click event doesn’t behave how I would expect when the width of the arc indicator is below half the size of the arc.
Code to reproduce
#include "radial_menu.h"
#define CHART_SIZE 160
#define SEGMENT_THICKNESS 40
#define SLICE_OFFSET 20
typedef struct {
int start_angle;
int end_angle;
int mid_angle;
lv_point_t home;
bool out;
} segment_info_t;
typedef struct {
lv_obj_t* obj;
int start_x;
int start_y;
int end_x;
int end_y;
} segment_anim_data_t;
static float angle_accum = 0.0f;
static segment_info_t* active_info = NULL;
static lv_obj_t* active_arc = NULL;
static void anim_move_cb(void* var, int32_t v) {
segment_anim_data_t* d = (segment_anim_data_t*)var;
int32_t x = d->start_x + ((d->end_x - d->start_x) * v) / 100;
int32_t y = d->start_y + ((d->end_y - d->start_y) * v) / 100;
lv_obj_set_pos(d->obj, x, y);
}
static void anim_cleanup_cb(lv_anim_t* a) {
lv_free(a->var);
}
static void arc_click_cb(lv_event_t* e) {
lv_obj_t* arc = lv_event_get_target_obj(e);
segment_info_t* info = (segment_info_t*)lv_event_get_user_data(e);
int32_t x_off = (SLICE_OFFSET * lv_trigo_cos(info->mid_angle)) >> LV_TRIGO_SHIFT;
int32_t y_off = (SLICE_OFFSET * lv_trigo_sin(info->mid_angle)) >> LV_TRIGO_SHIFT;
if (active_info && active_info != info && active_info->out) {
segment_anim_data_t* anim_back = (segment_anim_data_t*)lv_malloc(sizeof(segment_anim_data_t));
anim_back->obj = active_arc;
anim_back->start_x = lv_obj_get_x(active_arc) - SLICE_OFFSET;
anim_back->start_y = lv_obj_get_y(active_arc) - SLICE_OFFSET;
anim_back->end_x = active_info->home.x;
anim_back->end_y = active_info->home.y;
active_info->out = false;
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, anim_back);
lv_anim_set_exec_cb(&a, anim_move_cb);
lv_anim_set_time(&a, 50);
lv_anim_set_values(&a, 0, 100);
lv_anim_set_deleted_cb(&a, anim_cleanup_cb);
lv_anim_start(&a);
}
int target_x, target_y;
if (info->out) {
target_x = info->home.x;
target_y = info->home.y;
info->out = false;
active_info = NULL;
active_arc = NULL;
}
else {
target_x = info->home.x + x_off;
target_y = info->home.y + y_off;
info->out = true;
active_info = info;
active_arc = arc;
}
segment_anim_data_t* anim_data = (segment_anim_data_t*)lv_malloc(sizeof(segment_anim_data_t));
anim_data->obj = arc;
anim_data->start_x = lv_obj_get_x(arc) - SLICE_OFFSET;
anim_data->start_y = lv_obj_get_y(arc) - SLICE_OFFSET;
anim_data->end_x = target_x;
anim_data->end_y = target_y;
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, anim_data);
lv_anim_set_exec_cb(&a, anim_move_cb);
lv_anim_set_time(&a, 50);
lv_anim_set_values(&a, 0, 100);
lv_anim_set_deleted_cb(&a, anim_cleanup_cb);
lv_anim_start(&a);
}
static void create_segment(lv_obj_t* parent, const int segments) {
float segment_angle = 360.0f / segments;
float gap = 3.0f;
int start = (int)(angle_accum + 0.4f + gap / 2);
angle_accum += segment_angle;
int end = (int)(angle_accum + 0.5f - gap / 2);
if (end > 360) end = 360;
lv_obj_t* arc = lv_arc_create(parent);
lv_obj_set_size(arc, CHART_SIZE, CHART_SIZE);
lv_obj_center(arc);
lv_arc_set_mode(arc, LV_ARC_MODE_NORMAL);
lv_arc_set_bg_start_angle(arc, start);
lv_arc_set_bg_end_angle(arc, end);
lv_obj_set_style_arc_width(arc, SEGMENT_THICKNESS, 0);
///lv_obj_set_style_arc_width(arc, CHART_SIZE / 2, 0);
lv_obj_set_style_arc_width(arc, 0, LV_PART_INDICATOR);
lv_obj_set_style_arc_color(arc, lv_color_hex(0xff0000), 0);
lv_obj_set_style_arc_rounded(arc, false, 0);
lv_obj_remove_style(arc, NULL, LV_PART_KNOB);
lv_obj_add_flag(arc, LV_OBJ_FLAG_ADV_HITTEST);
/* for visualizing bounding box to debug */
lv_obj_set_style_border_width(arc, 1, 0);
lv_obj_set_style_border_color(arc, lv_color_hex(0x00FF00), 0);
lv_obj_set_style_bg_opa(arc, LV_OPA_20, 0);
lv_obj_set_style_bg_color(arc, lv_color_hex(0x0000FF), 0);
segment_info_t* info = (segment_info_t*) lv_malloc(sizeof(segment_info_t));
info->start_angle = start;
info->end_angle = end;
info->mid_angle = start + ((end - start) / 2);
info->out = false;
info->home.x = lv_obj_get_x(arc);
info->home.y = lv_obj_get_y(arc);
lv_obj_add_event_cb(arc, arc_click_cb, LV_EVENT_CLICKED, info);
}
lv_obj_t* radial_menu_create(lv_obj_t* parent, const int segments) {
lv_obj_t* root = lv_obj_create(parent);
lv_obj_set_size(root, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_center(root);
lv_obj_set_flex_flow(root, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(root, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_all(root, 0, 0);
lv_obj_set_style_border_width(root, 0, 0);
lv_obj_set_style_border_color(root, lv_color_hex(0xFF0000), 0);
lv_obj_set_style_bg_opa(root, LV_OPA_TRANSP, 0);
lv_obj_remove_flag(root, LV_OBJ_FLAG_SCROLLABLE);
/* slices container */
lv_obj_t* slices_container = lv_obj_create(root);
lv_obj_set_size(slices_container, CHART_SIZE + 2 * SLICE_OFFSET, CHART_SIZE + 2 * SLICE_OFFSET);
lv_obj_set_style_pad_all(slices_container, 0, 0);
lv_obj_set_style_margin_all(slices_container, 0, 0);
lv_obj_set_style_border_width(slices_container, 0, 0);
lv_obj_set_style_border_color(slices_container, lv_color_hex(0x00FF00), 0);
lv_obj_set_style_bg_opa(slices_container, LV_OPA_TRANSP, 0);
lv_obj_remove_flag(slices_container, LV_OBJ_FLAG_SCROLLABLE);
/* create segments */
angle_accum = 0.0f;
for (int i = 0; i < segments; ++i) {
create_segment(slices_container, segments);
}
return root;
}
Screenshot and/or video
Environment
- MCU/MPU/Board: WaveShare ESP32-C6 1.47inch Touch Display
- LVGL version: v9.3