From dd27c3530432ea0b09f01e604bf577f31d8ef841 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 1 Jun 2023 15:41:47 +1000 Subject: convert lvgl from submodule to a plain old directory --- lib/lvgl | 1 - lib/lvgl/src/extra/widgets/animimg/lv_animimg.c | 138 ++ lib/lvgl/src/extra/widgets/animimg/lv_animimg.h | 103 ++ lib/lvgl/src/extra/widgets/calendar/lv_calendar.c | 402 +++++ lib/lvgl/src/extra/widgets/calendar/lv_calendar.h | 164 ++ .../widgets/calendar/lv_calendar_header_arrow.c | 149 ++ .../widgets/calendar/lv_calendar_header_arrow.h | 49 + .../widgets/calendar/lv_calendar_header_dropdown.c | 142 ++ .../widgets/calendar/lv_calendar_header_dropdown.h | 49 + lib/lvgl/src/extra/widgets/chart/lv_chart.c | 1802 ++++++++++++++++++++ lib/lvgl/src/extra/widgets/chart/lv_chart.h | 460 +++++ .../src/extra/widgets/colorwheel/lv_colorwheel.c | 713 ++++++++ .../src/extra/widgets/colorwheel/lv_colorwheel.h | 142 ++ lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.c | 377 ++++ lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.h | 131 ++ lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.c | 430 +++++ lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.h | 187 ++ lib/lvgl/src/extra/widgets/led/lv_led.c | 221 +++ lib/lvgl/src/extra/widgets/led/lv_led.h | 116 ++ lib/lvgl/src/extra/widgets/list/lv_list.c | 120 ++ lib/lvgl/src/extra/widgets/list/lv_list.h | 54 + lib/lvgl/src/extra/widgets/lv_widgets.h | 56 + lib/lvgl/src/extra/widgets/menu/lv_menu.c | 767 +++++++++ lib/lvgl/src/extra/widgets/menu/lv_menu.h | 233 +++ lib/lvgl/src/extra/widgets/meter/lv_meter.c | 697 ++++++++ lib/lvgl/src/extra/widgets/meter/lv_meter.h | 267 +++ lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.c | 209 +++ lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.h | 99 ++ lib/lvgl/src/extra/widgets/span/lv_span.c | 1041 +++++++++++ lib/lvgl/src/extra/widgets/span/lv_span.h | 245 +++ lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.c | 516 ++++++ lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.h | 182 ++ lib/lvgl/src/extra/widgets/spinner/lv_spinner.c | 104 ++ lib/lvgl/src/extra/widgets/spinner/lv_spinner.h | 50 + lib/lvgl/src/extra/widgets/tabview/lv_tabview.c | 352 ++++ lib/lvgl/src/extra/widgets/tabview/lv_tabview.h | 65 + lib/lvgl/src/extra/widgets/tileview/lv_tileview.c | 194 +++ lib/lvgl/src/extra/widgets/tileview/lv_tileview.h | 72 + lib/lvgl/src/extra/widgets/win/lv_win.c | 110 ++ lib/lvgl/src/extra/widgets/win/lv_win.h | 51 + 40 files changed, 11259 insertions(+), 1 deletion(-) delete mode 160000 lib/lvgl create mode 100644 lib/lvgl/src/extra/widgets/animimg/lv_animimg.c create mode 100644 lib/lvgl/src/extra/widgets/animimg/lv_animimg.h create mode 100644 lib/lvgl/src/extra/widgets/calendar/lv_calendar.c create mode 100644 lib/lvgl/src/extra/widgets/calendar/lv_calendar.h create mode 100644 lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.c create mode 100644 lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.h create mode 100644 lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.c create mode 100644 lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.h create mode 100644 lib/lvgl/src/extra/widgets/chart/lv_chart.c create mode 100644 lib/lvgl/src/extra/widgets/chart/lv_chart.h create mode 100644 lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.c create mode 100644 lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.h create mode 100644 lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.c create mode 100644 lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.h create mode 100644 lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.c create mode 100644 lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.h create mode 100644 lib/lvgl/src/extra/widgets/led/lv_led.c create mode 100644 lib/lvgl/src/extra/widgets/led/lv_led.h create mode 100644 lib/lvgl/src/extra/widgets/list/lv_list.c create mode 100644 lib/lvgl/src/extra/widgets/list/lv_list.h create mode 100644 lib/lvgl/src/extra/widgets/lv_widgets.h create mode 100644 lib/lvgl/src/extra/widgets/menu/lv_menu.c create mode 100644 lib/lvgl/src/extra/widgets/menu/lv_menu.h create mode 100644 lib/lvgl/src/extra/widgets/meter/lv_meter.c create mode 100644 lib/lvgl/src/extra/widgets/meter/lv_meter.h create mode 100644 lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.c create mode 100644 lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.h create mode 100644 lib/lvgl/src/extra/widgets/span/lv_span.c create mode 100644 lib/lvgl/src/extra/widgets/span/lv_span.h create mode 100644 lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.c create mode 100644 lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.h create mode 100644 lib/lvgl/src/extra/widgets/spinner/lv_spinner.c create mode 100644 lib/lvgl/src/extra/widgets/spinner/lv_spinner.h create mode 100755 lib/lvgl/src/extra/widgets/tabview/lv_tabview.c create mode 100644 lib/lvgl/src/extra/widgets/tabview/lv_tabview.h create mode 100644 lib/lvgl/src/extra/widgets/tileview/lv_tileview.c create mode 100644 lib/lvgl/src/extra/widgets/tileview/lv_tileview.h create mode 100644 lib/lvgl/src/extra/widgets/win/lv_win.c create mode 100644 lib/lvgl/src/extra/widgets/win/lv_win.h (limited to 'lib/lvgl/src/extra/widgets') diff --git a/lib/lvgl b/lib/lvgl deleted file mode 160000 index 0732400e..00000000 --- a/lib/lvgl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0732400e7b564dd0e7dc4a924619d8e19c5b23a0 diff --git a/lib/lvgl/src/extra/widgets/animimg/lv_animimg.c b/lib/lvgl/src/extra/widgets/animimg/lv_animimg.c new file mode 100644 index 00000000..135a8a4b --- /dev/null +++ b/lib/lvgl/src/extra/widgets/animimg/lv_animimg.c @@ -0,0 +1,138 @@ +/** + * @file lv_animimg.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_animimg.h" +#if LV_USE_ANIMIMG != 0 + +/*Testing of dependencies*/ +#if LV_USE_IMG == 0 + #error "lv_animimg: lv_img is required. Enable it in lv_conf.h (LV_USE_IMG 1) " +#endif + +#include "../../../misc/lv_assert.h" +#include "../../../draw/lv_img_decoder.h" +#include "../../../misc/lv_fs.h" +#include "../../../misc/lv_txt.h" +#include "../../../misc/lv_math.h" +#include "../../../misc/lv_log.h" +#include "../../../misc/lv_anim.h" + +/********************* + * DEFINES + *********************/ +#define LV_OBJX_NAME "lv_animimg" + +#define MY_CLASS &lv_animimg_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void index_change(lv_obj_t * obj, int32_t index); +static void lv_animimg_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_animimg_class = { + .constructor_cb = lv_animimg_constructor, + .instance_size = sizeof(lv_animimg_t), + .base_class = &lv_img_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_animimg_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_animimg_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +void lv_animimg_set_src(lv_obj_t * obj, lv_img_dsc_t * dsc[], uint8_t num) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + animimg->dsc = dsc; + animimg->pic_count = num; + lv_anim_set_values(&animimg->anim, 0, num); +} + +void lv_animimg_start(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + lv_anim_start(&animimg->anim); +} + +/*===================== + * Setter functions + *====================*/ + +void lv_animimg_set_duration(lv_obj_t * obj, uint32_t duration) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + lv_anim_set_time(&animimg->anim, duration); + lv_anim_set_playback_delay(&animimg->anim, duration); +} + +void lv_animimg_set_repeat_count(lv_obj_t * obj, uint16_t count) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + lv_anim_set_repeat_count(&animimg->anim, count); +} + +/*===================== + * Getter functions + *====================*/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_animimg_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + LV_UNUSED(class_p); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + + animimg->dsc = NULL; + animimg->pic_count = -1; + //initial animation + lv_anim_init(&animimg->anim); + lv_anim_set_var(&animimg->anim, obj); + lv_anim_set_time(&animimg->anim, 30); + lv_anim_set_exec_cb(&animimg->anim, (lv_anim_exec_xcb_t)index_change); + lv_anim_set_values(&animimg->anim, 0, 1); + lv_anim_set_repeat_count(&animimg->anim, LV_ANIM_REPEAT_INFINITE); +} + +static void index_change(lv_obj_t * obj, int32_t index) +{ + lv_coord_t idx; + lv_animimg_t * animimg = (lv_animimg_t *)obj; + + idx = index % animimg->pic_count; + + lv_img_set_src(obj, animimg->dsc[idx]); +} + +#endif diff --git a/lib/lvgl/src/extra/widgets/animimg/lv_animimg.h b/lib/lvgl/src/extra/widgets/animimg/lv_animimg.h new file mode 100644 index 00000000..63294947 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/animimg/lv_animimg.h @@ -0,0 +1,103 @@ +/** + * @file lv_animimg.h + * + */ + +#ifndef LV_ANIM_IMG_H +#define LV_ANIM_IMG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_ANIMIMG != 0 + +/*Testing of dependencies*/ +#if LV_USE_IMG == 0 +#error "lv_animimg: lv_img is required. Enable it in lv_conf.h (LV_USE_IMG 1)" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +extern const lv_obj_class_t lv_animimg_class; + +/*Data of image*/ +typedef struct { + lv_img_t img; + lv_anim_t anim; + /*picture sequence */ + lv_img_dsc_t ** dsc; + int8_t pic_count; +} lv_animimg_t; + + +/*Image parts*/ +enum { + LV_ANIM_IMG_PART_MAIN, +}; +typedef uint8_t lv_animimg_part_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create an animation image objects + * @param parent pointer to an object, it will be the parent of the new button + * @return pointer to the created animation image object + */ +lv_obj_t * lv_animimg_create(lv_obj_t * parent); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the image animation images source. + * @param img pointer to an animation image object + * @param dsc pointer to a series images + * @param num images' number + */ +void lv_animimg_set_src(lv_obj_t * img, lv_img_dsc_t * dsc[], uint8_t num); + +/** + * Startup the image animation. + * @param obj pointer to an animation image object + */ +void lv_animimg_start(lv_obj_t * obj); + +/** + * Set the image animation duration time. unit:ms + * @param img pointer to an animation image object + */ +void lv_animimg_set_duration(lv_obj_t * img, uint32_t duration); + +/** + * Set the image animation reapeatly play times. + * @param img pointer to an animation image object + * @param count the number of times to repeat the animation + */ +void lv_animimg_set_repeat_count(lv_obj_t * img, uint16_t count); + +/*===================== + * Getter functions + *====================*/ + +#endif /*LV_USE_ANIMIMG*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_ANIM_IMG_H*/ diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar.c b/lib/lvgl/src/extra/widgets/calendar/lv_calendar.c new file mode 100644 index 00000000..b806d252 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar.c @@ -0,0 +1,402 @@ +/** + * @file lv_calendar.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_calendar.h" +#include "../../../lvgl.h" +#if LV_USE_CALENDAR + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define LV_CALENDAR_CTRL_TODAY LV_BTNMATRIX_CTRL_CUSTOM_1 +#define LV_CALENDAR_CTRL_HIGHLIGHT LV_BTNMATRIX_CTRL_CUSTOM_2 + +#define MY_CLASS &lv_calendar_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void draw_part_begin_event_cb(lv_event_t * e); + +static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day); +static uint8_t get_month_length(int32_t year, int32_t month); +static uint8_t is_leap_year(uint32_t year); +static void highlight_update(lv_obj_t * calendar); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_calendar_class = { + .constructor_cb = lv_calendar_constructor, + .width_def = (LV_DPI_DEF * 3) / 2, + .height_def = (LV_DPI_DEF * 3) / 2, + .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE, + .instance_size = sizeof(lv_calendar_t), + .base_class = &lv_obj_class +}; + +static const char * day_names_def[7] = LV_CALENDAR_DEFAULT_DAY_NAMES; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_calendar_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +void lv_calendar_set_day_names(lv_obj_t * obj, const char * day_names[]) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + uint32_t i; + for(i = 0; i < 7; i++) { + calendar->map[i] = day_names[i]; + } + lv_obj_invalidate(obj); +} + +void lv_calendar_set_today_date(lv_obj_t * obj, uint32_t year, uint32_t month, uint32_t day) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + calendar->today.year = year; + calendar->today.month = month; + calendar->today.day = day; + + highlight_update(obj); +} + +void lv_calendar_set_highlighted_dates(lv_obj_t * obj, lv_calendar_date_t highlighted[], uint16_t date_num) +{ + LV_ASSERT_NULL(highlighted); + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + calendar->highlighted_dates = highlighted; + calendar->highlighted_dates_num = date_num; + + highlight_update(obj); +} + +void lv_calendar_set_showed_date(lv_obj_t * obj, uint32_t year, uint32_t month) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + calendar->showed_date.year = year; + calendar->showed_date.month = month; + calendar->showed_date.day = 1; + + lv_calendar_date_t d; + d.year = calendar->showed_date.year; + d.month = calendar->showed_date.month; + d.day = calendar->showed_date.day; + + uint32_t i; + + /*Remove the disabled state but revert it for day names*/ + lv_btnmatrix_clear_btn_ctrl_all(calendar->btnm, LV_BTNMATRIX_CTRL_DISABLED); + for(i = 0; i < 7; i++) { + lv_btnmatrix_set_btn_ctrl(calendar->btnm, i, LV_BTNMATRIX_CTRL_DISABLED); + } + + uint8_t act_mo_len = get_month_length(d.year, d.month); + uint8_t day_first = get_day_of_week(d.year, d.month, 1); + uint8_t c; + for(i = day_first, c = 1; i < act_mo_len + day_first; i++, c++) { + lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c); + } + + uint8_t prev_mo_len = get_month_length(d.year, d.month - 1); + for(i = 0, c = prev_mo_len - day_first + 1; i < day_first; i++, c++) { + lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c); + lv_btnmatrix_set_btn_ctrl(calendar->btnm, i + 7, LV_BTNMATRIX_CTRL_DISABLED); + } + + for(i = day_first + act_mo_len, c = 1; i < 6 * 7; i++, c++) { + lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c); + lv_btnmatrix_set_btn_ctrl(calendar->btnm, i + 7, LV_BTNMATRIX_CTRL_DISABLED); + } + + highlight_update(obj); + + /*Reset the focused button if the days changes*/ + if(lv_btnmatrix_get_selected_btn(calendar->btnm) != LV_BTNMATRIX_BTN_NONE) { + lv_btnmatrix_set_selected_btn(calendar->btnm, day_first + 7); + } + + lv_obj_invalidate(obj); + + /* The children of the calendar are probably headers. + * Notify them to let the headers updated to the new date*/ + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + for(i = 0; i < child_cnt; i++) { + lv_obj_t * child = lv_obj_get_child(obj, i); + if(child == calendar->btnm) continue; + lv_event_send(child, LV_EVENT_VALUE_CHANGED, obj); + } +} + +/*===================== + * Getter functions + *====================*/ + +lv_obj_t * lv_calendar_get_btnmatrix(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + const lv_calendar_t * calendar = (lv_calendar_t *)obj; + return calendar->btnm; +} + +const lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + const lv_calendar_t * calendar = (lv_calendar_t *)obj; + + return &calendar->today; +} + +const lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + const lv_calendar_t * calendar = (lv_calendar_t *)obj; + + return &calendar->showed_date; +} + +lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + return calendar->highlighted_dates; +} + +uint16_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + return calendar->highlighted_dates_num; +} + +lv_res_t lv_calendar_get_pressed_date(const lv_obj_t * obj, lv_calendar_date_t * date) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + uint16_t d = lv_btnmatrix_get_selected_btn(calendar->btnm); + if(d == LV_BTNMATRIX_BTN_NONE) { + date->year = 0; + date->month = 0; + date->day = 0; + return LV_RES_INV; + } + + const char * txt = lv_btnmatrix_get_btn_text(calendar->btnm, lv_btnmatrix_get_selected_btn(calendar->btnm)); + + if(txt[1] == 0) date->day = txt[0] - '0'; + else date->day = (txt[0] - '0') * 10 + (txt[1] - '0'); + + date->year = calendar->showed_date.year; + date->month = calendar->showed_date.month; + + return LV_RES_OK; +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + /*Initialize the allocated 'ext'*/ + calendar->today.year = 2020; + calendar->today.month = 1; + calendar->today.day = 1; + + calendar->showed_date.year = 2020; + calendar->showed_date.month = 1; + calendar->showed_date.day = 1; + + calendar->highlighted_dates = NULL; + calendar->highlighted_dates_num = 0; + + lv_memset_00(calendar->nums, sizeof(calendar->nums)); + uint8_t i; + uint8_t j = 0; + for(i = 0; i < 8 * 7; i++) { + /*Every 8th string is "\n"*/ + if(i != 0 && (i + 1) % 8 == 0) { + calendar->map[i] = "\n"; + } + else if(i < 7) { + calendar->map[i] = day_names_def[i]; + } + else { + calendar->nums[j][0] = 'x'; + calendar->map[i] = calendar->nums[j]; + j++; + } + } + calendar->map[8 * 7 - 1] = ""; + + calendar->btnm = lv_btnmatrix_create(obj); + lv_btnmatrix_set_map(calendar->btnm, calendar->map); + lv_btnmatrix_set_btn_ctrl_all(calendar->btnm, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT); + lv_obj_add_event_cb(calendar->btnm, draw_part_begin_event_cb, LV_EVENT_DRAW_PART_BEGIN, NULL); + lv_obj_set_width(calendar->btnm, lv_pct(100)); + + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_grow(calendar->btnm, 1); + + lv_calendar_set_showed_date(obj, calendar->showed_date.year, calendar->showed_date.month); + lv_calendar_set_today_date(obj, calendar->today.year, calendar->today.month, calendar->today.day); + + lv_obj_add_flag(calendar->btnm, LV_OBJ_FLAG_EVENT_BUBBLE); +} + +static void draw_part_begin_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_obj_draw_part_dsc_t * dsc = lv_event_get_param(e); + if(dsc->part == LV_PART_ITEMS) { + /*Day name styles*/ + if(dsc->id < 7) { + dsc->rect_dsc->bg_opa = LV_OPA_TRANSP; + dsc->rect_dsc->border_opa = LV_OPA_TRANSP; + } + else if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_DISABLED)) { + dsc->rect_dsc->bg_opa = LV_OPA_TRANSP; + dsc->rect_dsc->border_opa = LV_OPA_TRANSP; + dsc->label_dsc->color = lv_palette_main(LV_PALETTE_GREY); + } + + if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_HIGHLIGHT)) { + dsc->rect_dsc->bg_opa = LV_OPA_40; + dsc->rect_dsc->bg_color = lv_theme_get_color_primary(obj); + if(lv_btnmatrix_get_selected_btn(obj) == dsc->id) { + dsc->rect_dsc->bg_opa = LV_OPA_70; + } + } + + if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_TODAY)) { + dsc->rect_dsc->border_opa = LV_OPA_COVER; + dsc->rect_dsc->border_color = lv_theme_get_color_primary(obj); + dsc->rect_dsc->border_width += 1; + } + + } +} + +/** + * Get the number of days in a month + * @param year a year + * @param month a month. The range is basically [1..12] but [-11..0] or [13..24] is also + * supported to handle next/prev. year + * @return [28..31] + */ +static uint8_t get_month_length(int32_t year, int32_t month) +{ + month--; + if(month < 0) { + year--; /*Already in the previous year (won't be less then -12 to skip a whole year)*/ + month = 12 + month; /*`month` is negative, the result will be < 12*/ + } + if(month >= 12) { + year++; + month -= 12; + } + + /*month == 1 is february*/ + return (month == 1) ? (28 + is_leap_year(year)) : 31 - month % 7 % 2; +} + +/** + * Tells whether a year is leap year or not + * @param year a year + * @return 0: not leap year; 1: leap year + */ +static uint8_t is_leap_year(uint32_t year) +{ + return (year % 4) || ((year % 100 == 0) && (year % 400)) ? 0 : 1; +} + +/** + * Get the day of the week + * @param year a year + * @param month a month [1..12] + * @param day a day [1..32] + * @return [0..6] which means [Sun..Sat] or [Mon..Sun] depending on LV_CALENDAR_WEEK_STARTS_MONDAY + */ +static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day) +{ + uint32_t a = month < 3 ? 1 : 0; + uint32_t b = year - a; + +#if LV_CALENDAR_WEEK_STARTS_MONDAY + uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400) - 1) % 7; +#else + uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400)) % 7; +#endif + + return day_of_week ; +} + +static void highlight_update(lv_obj_t * obj) +{ + lv_calendar_t * calendar = (lv_calendar_t *)obj; + uint16_t i; + + /*Clear all kind of selection*/ + lv_btnmatrix_clear_btn_ctrl_all(calendar->btnm, LV_CALENDAR_CTRL_TODAY | LV_CALENDAR_CTRL_HIGHLIGHT); + + uint8_t day_first = get_day_of_week(calendar->showed_date.year, calendar->showed_date.month, 1); + if(calendar->highlighted_dates) { + for(i = 0; i < calendar->highlighted_dates_num; i++) { + if(calendar->highlighted_dates[i].year == calendar->showed_date.year && + calendar->highlighted_dates[i].month == calendar->showed_date.month) { + lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->highlighted_dates[i].day - 1 + day_first + 7, + LV_CALENDAR_CTRL_HIGHLIGHT); + } + } + } + + if(calendar->showed_date.year == calendar->today.year && calendar->showed_date.month == calendar->today.month) { + lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->today.day - 1 + day_first + 7, LV_CALENDAR_CTRL_TODAY); + } +} + +#endif /*LV_USE_CALENDAR*/ diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar.h b/lib/lvgl/src/extra/widgets/calendar/lv_calendar.h new file mode 100644 index 00000000..2511b2fa --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar.h @@ -0,0 +1,164 @@ +/** + * @file lv_calendar.h + * + */ + +#ifndef LV_CALENDAR_H +#define LV_CALENDAR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../widgets/lv_btnmatrix.h" + +#if LV_USE_CALENDAR + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/** + * Represents a date on the calendar object (platform-agnostic). + */ +typedef struct { + uint16_t year; + int8_t month; /** 1..12*/ + int8_t day; /** 1..31*/ +} lv_calendar_date_t; + +/*Data of calendar*/ +typedef struct { + lv_obj_t obj; + lv_obj_t * btnm; + /*New data for this type*/ + lv_calendar_date_t today; /*Date of today*/ + lv_calendar_date_t showed_date; /*Currently visible month (day is ignored)*/ + lv_calendar_date_t * + highlighted_dates; /*Apply different style on these days (pointer to an array defined by the user)*/ + uint16_t highlighted_dates_num; /*Number of elements in `highlighted_days`*/ + const char * map[8 * 7]; + char nums [7 * 6][4]; +} lv_calendar_t; + +extern const lv_obj_class_t lv_calendar_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_calendar_create(lv_obj_t * parent); + +/*====================== + * Add/remove functions + *=====================*/ + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the today's date + * @param obj pointer to a calendar object + * @param year today's year + * @param month today's month [1..12] + * @param day today's day [1..31] + */ +void lv_calendar_set_today_date(lv_obj_t * obj, uint32_t year, uint32_t month, uint32_t day); + +/** + * Set the currently showed + * @param obj pointer to a calendar object + * @param year today's year + * @param month today's month [1..12] + */ +void lv_calendar_set_showed_date(lv_obj_t * obj, uint32_t year, uint32_t month); + +/** + * Set the highlighted dates + * @param obj pointer to a calendar object + * @param highlighted pointer to an `lv_calendar_date_t` array containing the dates. + * Only the pointer will be saved so this variable can't be local which will be destroyed later. + * @param date_num number of dates in the array + */ +void lv_calendar_set_highlighted_dates(lv_obj_t * obj, lv_calendar_date_t highlighted[], uint16_t date_num); + +/** + * Set the name of the days + * @param obj pointer to a calendar object + * @param day_names pointer to an array with the names. + * E.g. `const char * days[7] = {"Sun", "Mon", ...}` + * Only the pointer will be saved so this variable can't be local which will be destroyed later. + */ +void lv_calendar_set_day_names(lv_obj_t * obj, const char ** day_names); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get the button matrix object of the calendar. + * It shows the dates and day names. + * @param obj pointer to a calendar object + * @return pointer to a the button matrix + */ +lv_obj_t * lv_calendar_get_btnmatrix(const lv_obj_t * obj); + +/** + * Get the today's date + * @param calendar pointer to a calendar object + * @return return pointer to an `lv_calendar_date_t` variable containing the date of today. + */ +const lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * calendar); + +/** + * Get the currently showed + * @param calendar pointer to a calendar object + * @return pointer to an `lv_calendar_date_t` variable containing the date is being shown. + */ +const lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * calendar); + +/** + * Get the highlighted dates + * @param calendar pointer to a calendar object + * @return pointer to an `lv_calendar_date_t` array containing the dates. + */ +lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * calendar); + +/** + * Get the number of the highlighted dates + * @param calendar pointer to a calendar object + * @return number of highlighted days + */ +uint16_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * calendar); + +/** + * Get the currently pressed day + * @param calendar pointer to a calendar object + * @param date store the pressed date here + * @return LV_RES_OK: there is a valid pressed date; LV_RES_INV: there is no pressed data + */ +lv_res_t lv_calendar_get_pressed_date(const lv_obj_t * calendar, lv_calendar_date_t * date); + +/*===================== + * Other functions + *====================*/ + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_CALENDAR*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_CALENDAR_H*/ diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.c b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.c new file mode 100644 index 00000000..fecb1392 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.c @@ -0,0 +1,149 @@ +/** + * @file lv_calendar_header_arrow.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_calendar_header_arrow.h" +#if LV_USE_CALENDAR_HEADER_ARROW + +#include "lv_calendar.h" +#include "../../../widgets/lv_btn.h" +#include "../../../widgets/lv_label.h" +#include "../../layouts/flex/lv_flex.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void my_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void month_event_cb(lv_event_t * e); +static void value_changed_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_calendar_header_arrow_class = { + .base_class = &lv_obj_class, + .constructor_cb = my_constructor, + .width_def = LV_PCT(100), + .height_def = LV_DPI_DEF / 3 +}; + +static const char * month_names_def[12] = LV_CALENDAR_DEFAULT_MONTH_NAMES; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_calendar_header_arrow_create(lv_obj_t * parent) +{ + lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_header_arrow_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void my_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + LV_UNUSED(class_p); + + lv_obj_move_to_index(obj, 0); + + 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_START); + + lv_obj_t * mo_prev = lv_btn_create(obj); + lv_obj_set_style_bg_img_src(mo_prev, LV_SYMBOL_LEFT, 0); + lv_obj_set_height(mo_prev, lv_pct(100)); + lv_obj_update_layout(mo_prev); + lv_coord_t btn_size = lv_obj_get_height(mo_prev); + lv_obj_set_width(mo_prev, btn_size); + + lv_obj_add_event_cb(mo_prev, month_event_cb, LV_EVENT_CLICKED, NULL); + lv_obj_clear_flag(mo_prev, LV_OBJ_FLAG_CLICK_FOCUSABLE); + + lv_obj_t * label = lv_label_create(obj); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_flex_grow(label, 1); + + lv_obj_t * mo_next = lv_btn_create(obj); + lv_obj_set_style_bg_img_src(mo_next, LV_SYMBOL_RIGHT, 0); + lv_obj_set_size(mo_next, btn_size, btn_size); + + lv_obj_add_event_cb(mo_next, month_event_cb, LV_EVENT_CLICKED, NULL); + lv_obj_clear_flag(mo_next, LV_OBJ_FLAG_CLICK_FOCUSABLE); + + lv_obj_add_event_cb(obj, value_changed_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + /*Refresh the drop downs*/ + lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); +} + +static void month_event_cb(lv_event_t * e) +{ + lv_obj_t * btn = lv_event_get_target(e); + + lv_obj_t * header = lv_obj_get_parent(btn); + lv_obj_t * calendar = lv_obj_get_parent(header); + + const lv_calendar_date_t * d; + d = lv_calendar_get_showed_date(calendar); + lv_calendar_date_t newd = *d; + + /*The last child is the right button*/ + if(lv_obj_get_child(header, 0) == btn) { + if(newd.month == 1) { + newd.month = 12; + newd.year --; + } + else { + newd.month --; + } + } + else { + if(newd.month == 12) { + newd.month = 1; + newd.year ++; + } + else { + newd.month ++; + } + } + + lv_calendar_set_showed_date(calendar, newd.year, newd.month); + + lv_obj_t * label = lv_obj_get_child(header, 1); + lv_label_set_text_fmt(label, "%d %s", newd.year, month_names_def[newd.month - 1]); +} + +static void value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * header = lv_event_get_target(e); + lv_obj_t * calendar = lv_obj_get_parent(header); + + const lv_calendar_date_t * cur_date = lv_calendar_get_showed_date(calendar); + lv_obj_t * label = lv_obj_get_child(header, 1); + lv_label_set_text_fmt(label, "%d %s", cur_date->year, month_names_def[cur_date->month - 1]); +} + +#endif /*LV_USE_CALENDAR_HEADER_ARROW*/ + diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.h b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.h new file mode 100644 index 00000000..609ccb0c --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.h @@ -0,0 +1,49 @@ +/** + * @file lv_calendar_header_arrow.h + * + */ + +#ifndef LV_CALENDAR_HEADER_ARROW_H +#define LV_CALENDAR_HEADER_ARROW_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" +#if LV_USE_CALENDAR_HEADER_ARROW + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +extern const lv_obj_class_t lv_calendar_header_arrow_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a calendar header with drop-drowns to select the year and month + * @param parent pointer to a calendar object. + * @return the created header + */ +lv_obj_t * lv_calendar_header_arrow_create(lv_obj_t * parent); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_CALENDAR_HEADER_ARROW*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_CALENDAR_HEADER_ARROW_H*/ diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.c b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.c new file mode 100644 index 00000000..5e8f90d4 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.c @@ -0,0 +1,142 @@ +/** + * @file lv_calendar_obj_dropdown.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_calendar_header_dropdown.h" +#if LV_USE_CALENDAR_HEADER_DROPDOWN + +#include "lv_calendar.h" +#include "../../../widgets/lv_dropdown.h" +#include "../../layouts/flex/lv_flex.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void my_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void year_event_cb(lv_event_t * e); +static void month_event_cb(lv_event_t * e); +static void value_changed_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_calendar_header_dropdown_class = { + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT, + .constructor_cb = my_constructor +}; + +static const char * month_list = "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12"; +static const char * year_list = { + "2023\n2022\n2021\n" + "2020\n2019\n2018\n2017\n2016\n2015\n2014\n2013\n2012\n2011\n2010\n2009\n2008\n2007\n2006\n2005\n2004\n2003\n2002\n2001\n" + "2000\n1999\n1998\n1997\n1996\n1995\n1994\n1993\n1992\n1991\n1990\n1989\n1988\n1987\n1986\n1985\n1984\n1983\n1982\n1981\n" + "1980\n1979\n1978\n1977\n1976\n1975\n1974\n1973\n1972\n1971\n1970\n1969\n1968\n1967\n1966\n1965\n1964\n1963\n1962\n1961\n" + "1960\n1959\n1958\n1957\n1956\n1955\n1954\n1953\n1952\n1951\n1950\n1949\n1948\n1947\n1946\n1945\n1944\n1943\n1942\n1941\n" + "1940\n1939\n1938\n1937\n1936\n1935\n1934\n1933\n1932\n1931\n1930\n1929\n1928\n1927\n1926\n1925\n1924\n1923\n1922\n1921\n" + "1920\n1919\n1918\n1917\n1916\n1915\n1914\n1913\n1912\n1911\n1910\n1909\n1908\n1907\n1906\n1905\n1904\n1903\n1902\n1901" +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_calendar_header_dropdown_create(lv_obj_t * parent) +{ + lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_header_dropdown_class, parent); + lv_obj_class_init_obj(obj); + + return obj; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void my_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + LV_UNUSED(class_p); + + lv_obj_t * calendar = lv_obj_get_parent(obj); + lv_obj_move_to_index(obj, 0); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + + lv_obj_t * year_dd = lv_dropdown_create(obj); + lv_dropdown_set_options(year_dd, year_list); + lv_obj_add_event_cb(year_dd, year_event_cb, LV_EVENT_VALUE_CHANGED, calendar); + lv_obj_set_flex_grow(year_dd, 1); + + lv_obj_t * month_dd = lv_dropdown_create(obj); + lv_dropdown_set_options(month_dd, month_list); + lv_obj_add_event_cb(month_dd, month_event_cb, LV_EVENT_VALUE_CHANGED, calendar); + lv_obj_set_flex_grow(month_dd, 1); + + lv_obj_add_event_cb(obj, value_changed_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + /*Refresh the drop downs*/ + lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); +} + +static void month_event_cb(lv_event_t * e) +{ + lv_obj_t * dropdown = lv_event_get_target(e); + lv_obj_t * calendar = lv_event_get_user_data(e); + + uint16_t sel = lv_dropdown_get_selected(dropdown); + + const lv_calendar_date_t * d; + d = lv_calendar_get_showed_date(calendar); + lv_calendar_date_t newd = *d; + newd.month = sel + 1; + + lv_calendar_set_showed_date(calendar, newd.year, newd.month); +} + +static void year_event_cb(lv_event_t * e) +{ + lv_obj_t * dropdown = lv_event_get_target(e); + lv_obj_t * calendar = lv_event_get_user_data(e); + + uint16_t sel = lv_dropdown_get_selected(dropdown); + + const lv_calendar_date_t * d; + d = lv_calendar_get_showed_date(calendar); + lv_calendar_date_t newd = *d; + newd.year = 2023 - sel; + + lv_calendar_set_showed_date(calendar, newd.year, newd.month); +} + +static void value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * header = lv_event_get_target(e); + lv_obj_t * calendar = lv_obj_get_parent(header); + const lv_calendar_date_t * cur_date = lv_calendar_get_showed_date(calendar); + + lv_obj_t * year_dd = lv_obj_get_child(header, 0); + lv_dropdown_set_selected(year_dd, 2023 - cur_date->year); + + lv_obj_t * month_dd = lv_obj_get_child(header, 1); + lv_dropdown_set_selected(month_dd, cur_date->month - 1); +} + +#endif /*LV_USE_CALENDAR_HEADER_ARROW*/ + diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.h b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.h new file mode 100644 index 00000000..fca21976 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.h @@ -0,0 +1,49 @@ +/** + * @file lv_calendar_header_dropdown.h + * + */ + +#ifndef LV_CALENDAR_HEADER_DROPDOWN_H +#define LV_CALENDAR_HEADER_DROPDOWN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" +#if LV_USE_CALENDAR_HEADER_DROPDOWN + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +extern const lv_obj_class_t lv_calendar_header_dropdown_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a calendar header with drop-drowns to select the year and month + * @param parent pointer to a calendar object. + * @return the created header + */ +lv_obj_t * lv_calendar_header_dropdown_create(lv_obj_t * parent); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_CALENDAR_HEADER_ARROW*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_CALENDAR_HEADER_DROPDOWN_H*/ diff --git a/lib/lvgl/src/extra/widgets/chart/lv_chart.c b/lib/lvgl/src/extra/widgets/chart/lv_chart.c new file mode 100644 index 00000000..da6c18c0 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/chart/lv_chart.c @@ -0,0 +1,1802 @@ +/** + * @file lv_chart.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_chart.h" +#if LV_USE_CHART != 0 + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_chart_class + +#define LV_CHART_HDIV_DEF 3 +#define LV_CHART_VDIV_DEF 5 +#define LV_CHART_POINT_CNT_DEF 10 +#define LV_CHART_LABEL_MAX_TEXT_LENGTH 16 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e); + +static void draw_div_lines(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_series_line(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_series_bar(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_series_scatter(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x); +static void invalidate_point(lv_obj_t * obj, uint16_t i); +static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a); +lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_chart_class = { + .constructor_cb = lv_chart_constructor, + .destructor_cb = lv_chart_destructor, + .event_cb = lv_chart_event, + .width_def = LV_PCT(100), + .height_def = LV_DPI_DEF * 2, + .instance_size = sizeof(lv_chart_t), + .base_class = &lv_obj_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_chart_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->type == type) return; + + if(chart->type == LV_CHART_TYPE_SCATTER) { + lv_chart_series_t * ser; + _LV_LL_READ_BACK(&chart->series_ll, ser) { + lv_mem_free(ser->x_points); + ser->x_points = NULL; + } + } + + if(type == LV_CHART_TYPE_SCATTER) { + lv_chart_series_t * ser; + _LV_LL_READ_BACK(&chart->series_ll, ser) { + ser->x_points = lv_mem_alloc(sizeof(lv_point_t) * chart->point_cnt); + LV_ASSERT_MALLOC(ser->x_points); + if(ser->x_points == NULL) return; + } + } + + chart->type = type; + + lv_chart_refresh(obj); +} + +void lv_chart_set_point_count(lv_obj_t * obj, uint16_t cnt) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->point_cnt == cnt) return; + + lv_chart_series_t * ser; + + if(cnt < 1) cnt = 1; + + _LV_LL_READ_BACK(&chart->series_ll, ser) { + if(chart->type == LV_CHART_TYPE_SCATTER) { + if(!ser->x_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->x_points); + } + if(!ser->y_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->y_points); + ser->start_point = 0; + } + + chart->point_cnt = cnt; + + lv_chart_refresh(obj); +} + +void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t min, lv_coord_t max) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + max = max == min ? max + 1 : max; + + lv_chart_t * chart = (lv_chart_t *)obj; + switch(axis) { + case LV_CHART_AXIS_PRIMARY_Y: + chart->ymin[0] = min; + chart->ymax[0] = max; + break; + case LV_CHART_AXIS_SECONDARY_Y: + chart->ymin[1] = min; + chart->ymax[1] = max; + break; + case LV_CHART_AXIS_PRIMARY_X: + chart->xmin[0] = min; + chart->xmax[0] = max; + break; + case LV_CHART_AXIS_SECONDARY_X: + chart->xmin[1] = min; + chart->xmax[1] = max; + break; + default: + LV_LOG_WARN("Invalid axis: %d", axis); + return; + } + + lv_chart_refresh(obj); +} + +void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->update_mode == update_mode) return; + + chart->update_mode = update_mode; + lv_obj_invalidate(obj); +} + +void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->hdiv_cnt == hdiv && chart->vdiv_cnt == vdiv) return; + + chart->hdiv_cnt = hdiv; + chart->vdiv_cnt = vdiv; + + lv_obj_invalidate(obj); +} + + +void lv_chart_set_zoom_x(lv_obj_t * obj, uint16_t zoom_x) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->zoom_x == zoom_x) return; + + chart->zoom_x = zoom_x; + lv_obj_refresh_self_size(obj); + /*Be the chart doesn't remain scrolled out*/ + lv_obj_readjust_scroll(obj, LV_ANIM_OFF); + lv_obj_invalidate(obj); +} + +void lv_chart_set_zoom_y(lv_obj_t * obj, uint16_t zoom_y) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->zoom_y == zoom_y) return; + + chart->zoom_y = zoom_y; + lv_obj_refresh_self_size(obj); + /*Be the chart doesn't remain scrolled out*/ + lv_obj_readjust_scroll(obj, LV_ANIM_OFF); + lv_obj_invalidate(obj); +} + +uint16_t lv_chart_get_zoom_x(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->zoom_x; +} + +uint16_t lv_chart_get_zoom_y(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->zoom_y; +} + +void lv_chart_set_axis_tick(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t major_len, lv_coord_t minor_len, + lv_coord_t major_cnt, lv_coord_t minor_cnt, bool label_en, lv_coord_t draw_size) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis); + t->major_len = major_len; + t->minor_len = minor_len; + t->minor_cnt = minor_cnt; + t->major_cnt = major_cnt; + t->label_en = label_en; + t->draw_size = draw_size; + + lv_obj_refresh_ext_draw_size(obj); + lv_obj_invalidate(obj); +} + +lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->type; +} + +uint16_t lv_chart_get_point_count(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->point_cnt; +} + +uint16_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser) +{ + LV_UNUSED(obj); + LV_ASSERT_NULL(ser); + + return ser->start_point; +} + +void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_point_t * p_out) +{ + LV_ASSERT_NULL(obj); + LV_ASSERT_NULL(ser); + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(id >= chart->point_cnt) { + LV_LOG_WARN("Invalid index: %d", id); + p_out->x = 0; + p_out->y = 0; + return; + } + + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + + if(chart->type == LV_CHART_TYPE_LINE) { + p_out->x = (w * id) / (chart->point_cnt - 1); + } + else if(chart->type == LV_CHART_TYPE_SCATTER) { + p_out->x = lv_map(ser->x_points[id], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w); + } + else if(chart->type == LV_CHART_TYPE_BAR) { + uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll); + int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_ITEMS) * chart->zoom_x) >> 8; /*Gap between the column on the ~same X*/ + int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/ + lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt; + lv_coord_t col_w = block_w / ser_cnt; + + p_out->x = (int32_t)((int32_t)w * id) / chart->point_cnt; + + lv_chart_series_t * ser_i = NULL; + _LV_LL_READ_BACK(&chart->series_ll, ser_i) { + if(ser_i == ser) break; + p_out->x += col_w; + } + + p_out->x += (col_w - ser_gap) / 2; + } + + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + p_out->x += lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width; + p_out->x -= lv_obj_get_scroll_left(obj); + + int32_t temp_y = 0; + temp_y = (int32_t)((int32_t)ser->y_points[id] - chart->ymin[ser->y_axis_sec]) * h; + temp_y = temp_y / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]); + p_out->y = h - temp_y; + p_out->y += lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width; + p_out->y -= lv_obj_get_scroll_top(obj); +} + +void lv_chart_refresh(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_obj_invalidate(obj); +} + +/*====================== + * Series + *=====================*/ + +lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis) +{ + LV_LOG_INFO("begin"); + + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + lv_chart_series_t * ser = _lv_ll_ins_head(&chart->series_ll); + LV_ASSERT_MALLOC(ser); + if(ser == NULL) return NULL; + + lv_coord_t def = LV_CHART_POINT_NONE; + + ser->color = color; + ser->y_points = lv_mem_alloc(sizeof(lv_coord_t) * chart->point_cnt); + LV_ASSERT_MALLOC(ser->y_points); + + if(chart->type == LV_CHART_TYPE_SCATTER) { + ser->x_points = lv_mem_alloc(sizeof(lv_coord_t) * chart->point_cnt); + LV_ASSERT_MALLOC(ser->x_points); + } + if(ser->y_points == NULL) { + _lv_ll_remove(&chart->series_ll, ser); + lv_mem_free(ser); + return NULL; + } + + ser->start_point = 0; + ser->y_ext_buf_assigned = false; + ser->hidden = 0; + ser->x_axis_sec = axis & LV_CHART_AXIS_SECONDARY_X ? 1 : 0; + ser->y_axis_sec = axis & LV_CHART_AXIS_SECONDARY_Y ? 1 : 0; + + uint16_t i; + lv_coord_t * p_tmp = ser->y_points; + for(i = 0; i < chart->point_cnt; i++) { + *p_tmp = def; + p_tmp++; + } + + return ser; +} + +void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(series); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(!series->y_ext_buf_assigned && series->y_points) lv_mem_free(series->y_points); + + _lv_ll_remove(&chart->series_ll, series); + lv_mem_free(series); + + return; +} + +void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide) +{ + LV_ASSERT_OBJ(chart, MY_CLASS); + LV_ASSERT_NULL(series); + + series->hidden = hide ? 1 : 0; + lv_chart_refresh(chart); +} + + +void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color) +{ + LV_ASSERT_OBJ(chart, MY_CLASS); + LV_ASSERT_NULL(series); + + series->color = color; + lv_chart_refresh(chart); +} + +void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(id >= chart->point_cnt) return; + ser->start_point = id; +} + +lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * obj, const lv_chart_series_t * ser) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(ser == NULL) return _lv_ll_get_head(&chart->series_ll); + else return _lv_ll_get_next(&chart->series_ll, ser); +} + +/*===================== + * Cursor + *====================*/ + +/** + * Add a cursor with a given color + * @param chart pointer to chart object + * @param color color of the cursor + * @param dir direction of the cursor. `LV_DIR_RIGHT/LEFT/TOP/DOWN/HOR/VER/ALL`. OR-ed values are possible + * @return pointer to the created cursor + */ +lv_chart_cursor_t * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + lv_chart_cursor_t * cursor = _lv_ll_ins_head(&chart->cursor_ll); + LV_ASSERT_MALLOC(cursor); + if(cursor == NULL) return NULL; + + cursor->pos.x = LV_CHART_POINT_NONE; + cursor->pos.y = LV_CHART_POINT_NONE; + cursor->point_id = LV_CHART_POINT_NONE; + cursor->pos_set = 0; + cursor->color = color; + cursor->dir = dir; + + return cursor; +} + +/** + * Set the coordinate of the cursor with respect + * to the origin of series area of the chart. + * @param chart pointer to a chart object. + * @param cursor pointer to the cursor. + * @param pos the new coordinate of cursor relative to the series area + */ +void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos) +{ + LV_ASSERT_NULL(cursor); + LV_UNUSED(chart); + + cursor->pos.x = pos->x; + cursor->pos.y = pos->y; + cursor->pos_set = 1; + lv_chart_refresh(chart); +} + + +/** + * Set the coordinate of the cursor with respect + * to the origin of series area of the chart. + * @param chart pointer to a chart object. + * @param cursor pointer to the cursor. + * @param pos the new coordinate of cursor relative to the series area + */ +void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, uint16_t point_id) +{ + LV_ASSERT_NULL(cursor); + LV_UNUSED(chart); + + cursor->point_id = point_id; + cursor->pos_set = 0; + if(ser == NULL) ser = lv_chart_get_series_next(chart, NULL); + cursor->ser = ser; + lv_chart_refresh(chart); +} +/** + * Get the coordinate of the cursor with respect + * to the origin of series area of the chart. + * @param chart pointer to a chart object + * @param cursor pointer to cursor + * @return coordinate of the cursor as lv_point_t + */ +lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor) +{ + LV_ASSERT_NULL(cursor); + LV_UNUSED(chart); + + return cursor->pos; +} + +/*===================== + * Set/Get value(s) + *====================*/ + + +void lv_chart_set_all_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + lv_chart_t * chart = (lv_chart_t *)obj; + uint16_t i; + for(i = 0; i < chart->point_cnt; i++) { + ser->y_points[i] = value; + } + ser->start_point = 0; + lv_chart_refresh(obj); +} + +void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + lv_chart_t * chart = (lv_chart_t *)obj; + ser->y_points[ser->start_point] = value; + invalidate_point(obj, ser->start_point); + ser->start_point = (ser->start_point + 1) % chart->point_cnt; + invalidate_point(obj, ser->start_point); +} + +void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t x_value, lv_coord_t y_value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + lv_chart_t * chart = (lv_chart_t *)obj; + + if(chart->type != LV_CHART_TYPE_SCATTER) { + LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER"); + return; + } + + ser->x_points[ser->start_point] = x_value; + ser->y_points[ser->start_point] = y_value; + ser->start_point = (ser->start_point + 1) % chart->point_cnt; + invalidate_point(obj, ser->start_point); +} + +void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + lv_chart_t * chart = (lv_chart_t *)obj; + + if(id >= chart->point_cnt) return; + ser->y_points[id] = value; + invalidate_point(obj, id); +} + +void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t x_value, + lv_coord_t y_value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + lv_chart_t * chart = (lv_chart_t *)obj; + + if(chart->type != LV_CHART_TYPE_SCATTER) { + LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER"); + return; + } + + if(id >= chart->point_cnt) return; + ser->x_points[id] = x_value; + ser->y_points[id] = y_value; + invalidate_point(obj, id); +} + +void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[]) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + if(!ser->y_ext_buf_assigned && ser->y_points) lv_mem_free(ser->y_points); + ser->y_ext_buf_assigned = true; + ser->y_points = array; + lv_obj_invalidate(obj); +} + +void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[]) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + if(!ser->x_ext_buf_assigned && ser->x_points) lv_mem_free(ser->x_points); + ser->x_ext_buf_assigned = true; + ser->x_points = array; + lv_obj_invalidate(obj); +} + +lv_coord_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser) +{ + LV_UNUSED(obj); + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + return ser->y_points; +} + +lv_coord_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser) +{ + LV_UNUSED(obj); + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + return ser->x_points; +} + +uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->pressed_point_id; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_chart_t * chart = (lv_chart_t *)obj; + + _lv_ll_init(&chart->series_ll, sizeof(lv_chart_series_t)); + _lv_ll_init(&chart->cursor_ll, sizeof(lv_chart_cursor_t)); + + chart->ymin[0] = 0; + chart->xmin[0] = 0; + chart->ymin[1] = 0; + chart->xmin[1] = 0; + chart->ymax[0] = 100; + chart->xmax[0] = 100; + chart->ymax[1] = 100; + chart->xmax[1] = 100; + + chart->hdiv_cnt = LV_CHART_HDIV_DEF; + chart->vdiv_cnt = LV_CHART_VDIV_DEF; + chart->point_cnt = LV_CHART_POINT_CNT_DEF; + chart->pressed_point_id = LV_CHART_POINT_NONE; + chart->type = LV_CHART_TYPE_LINE; + chart->update_mode = LV_CHART_UPDATE_MODE_SHIFT; + chart->zoom_x = LV_IMG_ZOOM_NONE; + chart->zoom_y = LV_IMG_ZOOM_NONE; + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_chart_t * chart = (lv_chart_t *)obj; + lv_chart_series_t * ser; + while(chart->series_ll.head) { + ser = _lv_ll_get_head(&chart->series_ll); + + if(!ser->y_ext_buf_assigned) lv_mem_free(ser->y_points); + + _lv_ll_remove(&chart->series_ll, ser); + lv_mem_free(ser); + } + _lv_ll_clear(&chart->series_ll); + + lv_chart_cursor_t * cur; + while(chart->cursor_ll.head) { + cur = _lv_ll_get_head(&chart->cursor_ll); + _lv_ll_remove(&chart->cursor_ll, cur); + lv_mem_free(cur); + } + _lv_ll_clear(&chart->cursor_ll); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + /*Call the ancestor's event handler*/ + lv_res_t res; + + res = lv_obj_event_base(MY_CLASS, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(code == LV_EVENT_PRESSED) { + lv_indev_t * indev = lv_indev_get_act(); + lv_point_t p; + lv_indev_get_point(indev, &p); + + p.x -= obj->coords.x1; + uint32_t id = get_index_from_x(obj, p.x + lv_obj_get_scroll_left(obj)); + if(id != (uint32_t)chart->pressed_point_id) { + invalidate_point(obj, id); + invalidate_point(obj, chart->pressed_point_id); + chart->pressed_point_id = id; + lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + } + } + else if(code == LV_EVENT_RELEASED) { + invalidate_point(obj, chart->pressed_point_id); + chart->pressed_point_id = LV_CHART_POINT_NONE; + } + else if(code == LV_EVENT_SIZE_CHANGED) { + lv_obj_refresh_self_size(obj); + } + else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) { + lv_event_set_ext_draw_size(e, LV_MAX4(chart->tick[0].draw_size, chart->tick[1].draw_size, chart->tick[2].draw_size, + chart->tick[3].draw_size)); + } + else if(code == LV_EVENT_GET_SELF_SIZE) { + lv_point_t * p = lv_event_get_param(e); + p->x = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + p->y = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + } + else if(code == LV_EVENT_DRAW_MAIN) { + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + draw_div_lines(obj, draw_ctx); + draw_axes(obj, draw_ctx); + + if(_lv_ll_is_empty(&chart->series_ll) == false) { + if(chart->type == LV_CHART_TYPE_LINE) draw_series_line(obj, draw_ctx); + else if(chart->type == LV_CHART_TYPE_BAR) draw_series_bar(obj, draw_ctx); + else if(chart->type == LV_CHART_TYPE_SCATTER) draw_series_scatter(obj, draw_ctx); + } + + draw_cursors(obj, draw_ctx); + } +} + +static void draw_div_lines(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + + lv_area_t series_clip_area; + bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, draw_ctx->clip_area); + if(mask_ret == false) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &series_clip_area; + + int16_t i; + int16_t i_start; + int16_t i_end; + lv_point_t p1; + lv_point_t p2; + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width; + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width; + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc); + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.part = LV_PART_MAIN; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_INIT; + part_draw_dsc.line_dsc = &line_dsc; + part_draw_dsc.id = 0xFFFFFFFF; + part_draw_dsc.p1 = NULL; + part_draw_dsc.p2 = NULL; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + lv_opa_t border_opa = lv_obj_get_style_border_opa(obj, LV_PART_MAIN); + lv_coord_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_border_side_t border_side = lv_obj_get_style_border_side(obj, LV_PART_MAIN); + + lv_coord_t scroll_left = lv_obj_get_scroll_left(obj); + lv_coord_t scroll_top = lv_obj_get_scroll_top(obj); + if(chart->hdiv_cnt != 0) { + lv_coord_t y_ofs = obj->coords.y1 + pad_top - scroll_top; + p1.x = obj->coords.x1; + p2.x = obj->coords.x2; + + i_start = 0; + i_end = chart->hdiv_cnt; + if(border_opa > LV_OPA_MIN && border_w > 0) { + if((border_side & LV_BORDER_SIDE_TOP) && (lv_obj_get_style_pad_top(obj, LV_PART_MAIN) == 0)) i_start++; + if((border_side & LV_BORDER_SIDE_BOTTOM) && (lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) == 0)) i_end--; + } + + for(i = i_start; i < i_end; i++) { + p1.y = (int32_t)((int32_t)h * i) / (chart->hdiv_cnt - 1); + p1.y += y_ofs; + p2.y = p1.y; + + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_HOR; + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + part_draw_dsc.id = i; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + + if(chart->vdiv_cnt != 0) { + lv_coord_t x_ofs = obj->coords.x1 + pad_left - scroll_left; + p1.y = obj->coords.y1; + p2.y = obj->coords.y2; + i_start = 0; + i_end = chart->vdiv_cnt; + if(border_opa > LV_OPA_MIN && border_w > 0) { + if((border_side & LV_BORDER_SIDE_LEFT) && (lv_obj_get_style_pad_left(obj, LV_PART_MAIN) == 0)) i_start++; + if((border_side & LV_BORDER_SIDE_RIGHT) && (lv_obj_get_style_pad_right(obj, LV_PART_MAIN) == 0)) i_end--; + } + + for(i = i_start; i < i_end; i++) { + p1.x = (int32_t)((int32_t)w * i) / (chart->vdiv_cnt - 1); + p1.x += x_ofs; + p2.x = p1.x; + + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_VER; + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + part_draw_dsc.id = i; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + + part_draw_dsc.id = 0xFFFFFFFF; + part_draw_dsc.p1 = NULL; + part_draw_dsc.p2 = NULL; + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_series_line(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + lv_area_t clip_area; + if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->point_cnt < 2) return; + + uint16_t i; + lv_point_t p1; + lv_point_t p2; + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width; + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width; + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj); + lv_coord_t y_ofs = obj->coords.y1 + pad_top - lv_obj_get_scroll_top(obj); + lv_chart_series_t * ser; + + lv_area_t series_clip_area; + bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, draw_ctx->clip_area); + if(mask_ret == false) return; + + lv_draw_line_dsc_t line_dsc_default; + lv_draw_line_dsc_init(&line_dsc_default); + lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc_default); + + lv_draw_rect_dsc_t point_dsc_default; + lv_draw_rect_dsc_init(&point_dsc_default); + lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default); + + lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2; + lv_coord_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2; + + /*Do not bother with line ending is the point will over it*/ + if(LV_MIN(point_w, point_h) > line_dsc_default.width / 2) line_dsc_default.raw_end = 1; + if(line_dsc_default.width == 1) line_dsc_default.raw_end = 1; + + /*If there are at least as much points as pixels then draw only vertical lines*/ + bool crowded_mode = chart->point_cnt >= w ? true : false; + + /*Go through all data lines*/ + _LV_LL_READ_BACK(&chart->series_ll, ser) { + if(ser->hidden) continue; + line_dsc_default.color = ser->color; + point_dsc_default.bg_color = ser->color; + + lv_coord_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0; + + p1.x = x_ofs; + p2.x = x_ofs; + + lv_coord_t p_act = start_point; + lv_coord_t p_prev = start_point; + int32_t y_tmp = (int32_t)((int32_t)ser->y_points[p_prev] - chart->ymin[ser->y_axis_sec]) * h; + y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]); + p2.y = h - y_tmp + y_ofs; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_LINE_AND_POINT; + part_draw_dsc.part = LV_PART_ITEMS; + part_draw_dsc.line_dsc = &line_dsc_default; + part_draw_dsc.rect_dsc = &point_dsc_default; + part_draw_dsc.sub_part_ptr = ser; + + lv_coord_t y_min = p2.y; + lv_coord_t y_max = p2.y; + + for(i = 0; i < chart->point_cnt; i++) { + p1.x = p2.x; + p1.y = p2.y; + + if(p1.x > clip_area_ori->x2 + point_w + 1) break; + p2.x = ((w * i) / (chart->point_cnt - 1)) + x_ofs; + + p_act = (start_point + i) % chart->point_cnt; + + y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h; + y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]); + p2.y = h - y_tmp + y_ofs; + + if(p2.x < clip_area_ori->x1 - point_w - 1) { + p_prev = p_act; + continue; + } + + /*Don't draw the first point. A second point is also required to draw the line*/ + if(i != 0) { + if(crowded_mode) { + if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) { + /*Draw only one vertical line between the min and max y-values on the same x-value*/ + y_max = LV_MAX(y_max, p2.y); + y_min = LV_MIN(y_min, p2.y); + if(p1.x != p2.x) { + lv_coord_t y_cur = p2.y; + p2.x--; /*It's already on the next x value*/ + p1.x = p2.x; + p1.y = y_min; + p2.y = y_max; + if(p1.y == p2.y) p2.y++; /*If they are the same no line will be drawn*/ + lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2); + p2.x++; /*Compensate the previous x--*/ + y_min = y_cur; /*Start the line of the next x from the current last y*/ + y_max = y_cur; + } + } + } + else { + lv_area_t point_area; + point_area.x1 = p1.x - point_w; + point_area.x2 = p1.x + point_w; + point_area.y1 = p1.y - point_h; + point_area.y2 = p1.y + point_h; + + part_draw_dsc.id = i - 1; + part_draw_dsc.p1 = ser->y_points[p_prev] != LV_CHART_POINT_NONE ? &p1 : NULL; + part_draw_dsc.p2 = ser->y_points[p_act] != LV_CHART_POINT_NONE ? &p2 : NULL; + part_draw_dsc.draw_area = &point_area; + part_draw_dsc.value = ser->y_points[p_prev]; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) { + lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2); + } + + if(point_w && point_h && ser->y_points[p_prev] != LV_CHART_POINT_NONE) { + lv_draw_rect(draw_ctx, &point_dsc_default, &point_area); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + + } + p_prev = p_act; + } + + /*Draw the last point*/ + if(!crowded_mode && i == chart->point_cnt) { + + if(ser->y_points[p_act] != LV_CHART_POINT_NONE) { + lv_area_t point_area; + point_area.x1 = p2.x - point_w; + point_area.x2 = p2.x + point_w; + point_area.y1 = p2.y - point_h; + point_area.y2 = p2.y + point_h; + + part_draw_dsc.id = i - 1; + part_draw_dsc.p1 = NULL; + part_draw_dsc.p2 = NULL; + part_draw_dsc.draw_area = &point_area; + part_draw_dsc.value = ser->y_points[p_act]; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_rect(draw_ctx, &point_dsc_default, &point_area); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + } + + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_series_scatter(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + + lv_area_t clip_area; + if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + lv_chart_t * chart = (lv_chart_t *)obj; + + uint16_t i; + lv_point_t p1; + lv_point_t p2; + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN); + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + lv_coord_t x_ofs = obj->coords.x1 + pad_left + border_width - lv_obj_get_scroll_left(obj); + lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj); + lv_chart_series_t * ser; + + lv_draw_line_dsc_t line_dsc_default; + lv_draw_line_dsc_init(&line_dsc_default); + lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc_default); + + lv_draw_rect_dsc_t point_dsc_default; + lv_draw_rect_dsc_init(&point_dsc_default); + lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default); + + lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2; + lv_coord_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2; + + /*Do not bother with line ending is the point will over it*/ + if(LV_MIN(point_w, point_h) > line_dsc_default.width / 2) line_dsc_default.raw_end = 1; + if(line_dsc_default.width == 1) line_dsc_default.raw_end = 1; + + /*Go through all data lines*/ + _LV_LL_READ_BACK(&chart->series_ll, ser) { + if(ser->hidden) continue; + line_dsc_default.color = ser->color; + point_dsc_default.bg_color = ser->color; + + lv_coord_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0; + + p1.x = x_ofs; + p2.x = x_ofs; + + lv_coord_t p_act = start_point; + lv_coord_t p_prev = start_point; + if(ser->y_points[p_act] != LV_CHART_POINT_CNT_DEF) { + p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w); + p2.x += x_ofs; + + p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h); + p2.y = h - p2.y; + p2.y += y_ofs; + } + else { + p2.x = LV_COORD_MIN; + p2.y = LV_COORD_MIN; + } + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.part = LV_PART_ITEMS; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_LINE_AND_POINT; + part_draw_dsc.line_dsc = &line_dsc_default; + part_draw_dsc.rect_dsc = &point_dsc_default; + part_draw_dsc.sub_part_ptr = ser; + + for(i = 0; i < chart->point_cnt; i++) { + p1.x = p2.x; + p1.y = p2.y; + + p_act = (start_point + i) % chart->point_cnt; + if(ser->y_points[p_act] != LV_CHART_POINT_NONE) { + p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h); + p2.y = h - p2.y; + p2.y += y_ofs; + + p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w); + p2.x += x_ofs; + } + else { + p_prev = p_act; + continue; + } + + /*Don't draw the first point. A second point is also required to draw the line*/ + if(i != 0) { + lv_area_t point_area; + point_area.x1 = p1.x - point_w; + point_area.x2 = p1.x + point_w; + point_area.y1 = p1.y - point_h; + point_area.y2 = p1.y + point_h; + + part_draw_dsc.id = i - 1; + part_draw_dsc.p1 = ser->y_points[p_prev] != LV_CHART_POINT_NONE ? &p1 : NULL; + part_draw_dsc.p2 = ser->y_points[p_act] != LV_CHART_POINT_NONE ? &p2 : NULL; + part_draw_dsc.draw_area = &point_area; + part_draw_dsc.value = ser->y_points[p_prev]; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) { + lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2); + if(point_w && point_h) { + lv_draw_rect(draw_ctx, &point_dsc_default, &point_area); + } + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + p_prev = p_act; + } + + /*Draw the last point*/ + if(i == chart->point_cnt) { + + if(ser->y_points[p_act] != LV_CHART_POINT_NONE) { + lv_area_t point_area; + point_area.x1 = p2.x - point_w; + point_area.x2 = p2.x + point_w; + point_area.y1 = p2.y - point_h; + point_area.y2 = p2.y + point_h; + + part_draw_dsc.id = i - 1; + part_draw_dsc.p1 = NULL; + part_draw_dsc.p2 = NULL; + part_draw_dsc.draw_area = &point_area; + part_draw_dsc.value = ser->y_points[p_act]; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_rect(draw_ctx, &point_dsc_default, &point_area); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + } + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_series_bar(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + lv_area_t clip_area; + if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + + lv_chart_t * chart = (lv_chart_t *)obj; + + uint16_t i; + lv_area_t col_a; + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN); + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + int32_t y_tmp; + lv_chart_series_t * ser; + uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll); + int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/ + lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt; + int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_ITEMS) * chart->zoom_x) >> 8; /*Gap between the columns on the ~same X*/ + lv_coord_t col_w = (block_w - (ser_cnt - 1) * ser_gap) / ser_cnt; + + lv_coord_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t x_ofs = pad_left - lv_obj_get_scroll_left(obj) + border_w; + lv_coord_t y_ofs = pad_top - lv_obj_get_scroll_top(obj) + border_w; + + lv_draw_rect_dsc_t col_dsc; + lv_draw_rect_dsc_init(&col_dsc); + lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc); + col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE; + col_dsc.bg_opa = LV_OPA_COVER; + + /*Make the cols longer with `radius` to clip the rounding from the bottom*/ + col_a.y2 = obj->coords.y2 + col_dsc.radius; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.part = LV_PART_ITEMS; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_BAR; + + /*Go through all points*/ + for(i = 0; i < chart->point_cnt; i++) { + lv_coord_t x_act = (int32_t)((int32_t)(w - block_w) * i) / (chart->point_cnt - 1) + obj->coords.x1 + x_ofs; + + part_draw_dsc.id = i; + + /*Draw the current point of all data line*/ + _LV_LL_READ_BACK(&chart->series_ll, ser) { + if(ser->hidden) continue; + lv_coord_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0; + + col_a.x1 = x_act; + col_a.x2 = col_a.x1 + col_w - 1; + x_act += col_w + ser_gap; + + if(col_a.x2 < clip_area.x1) continue; + if(col_a.x1 > clip_area.x2) break; + + col_dsc.bg_color = ser->color; + + lv_coord_t p_act = (start_point + i) % chart->point_cnt; + y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h; + y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]); + col_a.y1 = h - y_tmp + obj->coords.y1 + y_ofs; + + if(ser->y_points[p_act] != LV_CHART_POINT_NONE) { + part_draw_dsc.draw_area = &col_a; + part_draw_dsc.rect_dsc = &col_dsc; + part_draw_dsc.sub_part_ptr = ser; + part_draw_dsc.value = ser->y_points[p_act]; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_rect(draw_ctx, &col_dsc, &col_a); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + } + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(_lv_ll_is_empty(&chart->cursor_ll)) return; + + lv_area_t clip_area; + if(!_lv_area_intersect(&clip_area, draw_ctx->clip_area, &obj->coords)) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + lv_point_t p1; + lv_point_t p2; + lv_chart_cursor_t * cursor; + + lv_draw_line_dsc_t line_dsc_ori; + lv_draw_line_dsc_init(&line_dsc_ori); + lv_obj_init_draw_line_dsc(obj, LV_PART_CURSOR, &line_dsc_ori); + + lv_draw_rect_dsc_t point_dsc_ori; + lv_draw_rect_dsc_init(&point_dsc_ori); + point_dsc_ori.bg_opa = line_dsc_ori.opa; + point_dsc_ori.radius = LV_RADIUS_CIRCLE; + + lv_draw_line_dsc_t line_dsc_tmp; + lv_draw_rect_dsc_t point_dsc_tmp; + + lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2; + lv_coord_t point_h = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.line_dsc = &line_dsc_tmp; + part_draw_dsc.rect_dsc = &point_dsc_tmp; + part_draw_dsc.part = LV_PART_CURSOR; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_CURSOR; + + /*Go through all cursor lines*/ + _LV_LL_READ_BACK(&chart->cursor_ll, cursor) { + lv_memcpy(&line_dsc_tmp, &line_dsc_ori, sizeof(lv_draw_line_dsc_t)); + lv_memcpy(&point_dsc_tmp, &point_dsc_ori, sizeof(lv_draw_rect_dsc_t)); + line_dsc_tmp.color = cursor->color; + point_dsc_tmp.bg_color = cursor->color; + + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + + lv_coord_t cx; + lv_coord_t cy; + if(cursor->pos_set) { + cx = cursor->pos.x; + cy = cursor->pos.y; + } + else { + if(cursor->point_id == LV_CHART_POINT_NONE) continue; + lv_point_t p; + lv_chart_get_point_pos_by_id(obj, cursor->ser, cursor->point_id, &p); + cx = p.x; + cy = p.y; + } + + cx += obj->coords.x1; + cy += obj->coords.y1; + + lv_area_t point_area; + bool draw_point = point_w && point_h; + if(draw_point) { + point_area.x1 = cx - point_w; + point_area.x2 = cx + point_w; + point_area.y1 = cy - point_h; + point_area.y2 = cy + point_h; + + part_draw_dsc.draw_area = &point_area; + } + else { + part_draw_dsc.draw_area = NULL; + } + + if(cursor->dir & LV_DIR_HOR) { + p1.x = cursor->dir & LV_DIR_LEFT ? obj->coords.x1 : cx; + p1.y = cy; + p2.x = cursor->dir & LV_DIR_RIGHT ? obj->coords.x2 : cx; + p2.y = p1.y; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2); + + if(draw_point) { + lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + + if(cursor->dir & LV_DIR_VER) { + p1.x = cx; + p1.y = cursor->dir & LV_DIR_TOP ? obj->coords.y1 : cy; + p2.x = p1.x; + p2.y = cursor->dir & LV_DIR_BOTTOM ? obj->coords.y2 : cy; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2); + + if(draw_point) { + lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_y_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + + lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis); + + if(t->major_cnt <= 1) return; + if(!t->label_en && !t->major_len && !t->minor_len) return; + + uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_Y ? 0 : 1; + + uint32_t i; + + lv_point_t p1; + lv_point_t p2; + + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN); + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj); + + lv_coord_t label_gap; + lv_coord_t x_ofs; + if(axis == LV_CHART_AXIS_PRIMARY_Y) { + label_gap = lv_obj_get_style_pad_left(obj, LV_PART_TICKS); + x_ofs = obj->coords.x1; + } + else { + label_gap = lv_obj_get_style_pad_right(obj, LV_PART_TICKS); + x_ofs = obj->coords.x2; + } + + lv_coord_t major_len = t->major_len; + lv_coord_t minor_len = t->minor_len; + /*tick lines on secondary y axis are drawn in other direction*/ + if(axis == LV_CHART_AXIS_SECONDARY_Y) { + major_len *= -1; + minor_len *= -1; + } + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc); + + lv_draw_label_dsc_t label_dsc; + lv_draw_label_dsc_init(&label_dsc); + lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc); + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL; + part_draw_dsc.id = axis; + part_draw_dsc.part = LV_PART_TICKS; + part_draw_dsc.line_dsc = &line_dsc; + part_draw_dsc.label_dsc = &label_dsc; + + uint32_t total_tick_num = (t->major_cnt - 1) * (t->minor_cnt); + for(i = 0; i <= total_tick_num; i++) { + /*draw a line at moving y position*/ + p2.y = p1.y = y_ofs + (int32_t)((int32_t)(h - line_dsc.width) * i) / total_tick_num; + + /*first point of the tick*/ + p1.x = x_ofs; + + /*move extra pixel out of chart boundary*/ + if(axis == LV_CHART_AXIS_PRIMARY_Y) p1.x--; + else p1.x++; + + /*second point of the tick*/ + bool major = false; + if(i % t->minor_cnt == 0) major = true; + + if(major) p2.x = p1.x - major_len; /*major tick*/ + else p2.x = p1.x - minor_len; /*minor tick*/ + + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + + int32_t tick_value = lv_map(total_tick_num - i, 0, total_tick_num, chart->ymin[sec_axis], chart->ymax[sec_axis]); + part_draw_dsc.value = tick_value; + + /*add text only to major tick*/ + if(major && t->label_en) { + char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH]; + lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value); + part_draw_dsc.label_dsc = &label_dsc; + part_draw_dsc.text = buf; + part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + /*reserve appropriate area*/ + lv_point_t size; + lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX, + LV_TEXT_FLAG_NONE); + + /*set the area at some distance of the major tick len left of the tick*/ + lv_area_t a; + a.y1 = p2.y - size.y / 2; + a.y2 = p2.y + size.y / 2; + + if(!sec_axis) { + a.x1 = p2.x - size.x - label_gap; + a.x2 = p2.x - label_gap; + } + else { + a.x1 = p2.x + label_gap; + a.x2 = p2.x + size.x + label_gap; + } + + if(a.y2 >= obj->coords.y1 && + a.y1 <= obj->coords.y2) { + lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL); + } + } + else { + part_draw_dsc.label_dsc = NULL; + part_draw_dsc.text = NULL; + part_draw_dsc.text_length = 0; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + } + + if(p1.y + line_dsc.width / 2 >= obj->coords.y1 && + p2.y - line_dsc.width / 2 <= obj->coords.y2) { + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } +} + +static void draw_x_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + + lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis); + if(t->major_cnt <= 1) return; + if(!t->label_en && !t->major_len && !t->minor_len) return; + + uint32_t i; + lv_point_t p1; + lv_point_t p2; + + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + + lv_draw_label_dsc_t label_dsc; + lv_draw_label_dsc_init(&label_dsc); + lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc); + + lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj); + lv_coord_t y_ofs; + lv_coord_t label_gap; + if(axis == LV_CHART_AXIS_PRIMARY_X) { + label_gap = t->label_en ? lv_obj_get_style_pad_bottom(obj, LV_PART_TICKS) : 0; + y_ofs = obj->coords.y2; + } + else { + label_gap = t->label_en ? lv_obj_get_style_pad_top(obj, LV_PART_TICKS) : 0; + y_ofs = obj->coords.y1; + } + + if(axis == LV_CHART_AXIS_PRIMARY_X) { + if(y_ofs > draw_ctx->clip_area->y2) return; + if(y_ofs + label_gap + label_dsc.font->line_height + t->major_len < draw_ctx->clip_area->y1) return; + } + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc); + line_dsc.dash_gap = 0; + line_dsc.dash_width = 0; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL; + part_draw_dsc.id = LV_CHART_AXIS_PRIMARY_X; + part_draw_dsc.part = LV_PART_TICKS; + part_draw_dsc.label_dsc = &label_dsc; + part_draw_dsc.line_dsc = &line_dsc; + + uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_X ? 0 : 1; + + /*The columns ticks should be aligned to the center of blocks*/ + if(chart->type == LV_CHART_TYPE_BAR) { + int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the columns on ~adjacent X*/ + lv_coord_t block_w = (w + block_gap) / (chart->point_cnt); + + x_ofs += (block_w - block_gap) / 2; + w -= block_w - block_gap; + } + + p1.y = y_ofs; + uint32_t total_tick_num = (t->major_cnt - 1) * t->minor_cnt; + for(i = 0; i <= total_tick_num; i++) { /*one extra loop - it may not exist in the list, empty label*/ + bool major = false; + if(i % t->minor_cnt == 0) major = true; + + /*draw a line at moving x position*/ + p2.x = p1.x = x_ofs + (int32_t)((int32_t)(w - line_dsc.width) * i) / total_tick_num; + + if(sec_axis) p2.y = p1.y - (major ? t->major_len : t->minor_len); + else p2.y = p1.y + (major ? t->major_len : t->minor_len); + + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + + /*add text only to major tick*/ + int32_t tick_value; + if(chart->type == LV_CHART_TYPE_SCATTER) { + tick_value = lv_map(i, 0, total_tick_num, chart->xmin[sec_axis], chart->xmax[sec_axis]); + } + else { + tick_value = i / t->minor_cnt; + } + part_draw_dsc.value = tick_value; + + if(major && t->label_en) { + char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH]; + lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value); + part_draw_dsc.label_dsc = &label_dsc; + part_draw_dsc.text = buf; + part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + /*reserve appropriate area*/ + lv_point_t size; + lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX, + LV_TEXT_FLAG_NONE); + + /*set the area at some distance of the major tick len under of the tick*/ + lv_area_t a; + a.x1 = (p2.x - size.x / 2); + a.x2 = (p2.x + size.x / 2); + if(sec_axis) { + a.y2 = p2.y - label_gap; + a.y1 = a.y2 - size.y; + } + else { + a.y1 = p2.y + label_gap; + a.y2 = a.y1 + size.y; + } + + if(a.x2 >= obj->coords.x1 && + a.x1 <= obj->coords.x2) { + lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL); + } + } + else { + part_draw_dsc.label_dsc = NULL; + part_draw_dsc.text = NULL; + part_draw_dsc.text_length = 0; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + } + + + if(p1.x + line_dsc.width / 2 >= obj->coords.x1 && + p2.x - line_dsc.width / 2 <= obj->coords.x2) { + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } +} + +static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_Y); + draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_Y); + draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_X); + draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_X); +} + +/** + * Get the nearest index to an X coordinate + * @param chart pointer to a chart object + * @param coord the coordination of the point relative to the series area. + * @return the found index + */ +static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + x -= pad_left; + + if(x < 0) return 0; + if(x > w) return chart->point_cnt - 1; + if(chart->type == LV_CHART_TYPE_LINE) return (x * (chart->point_cnt - 1) + w / 2) / w; + if(chart->type == LV_CHART_TYPE_BAR) return (x * chart->point_cnt) / w; + + return 0; +} + +static void invalidate_point(lv_obj_t * obj, uint16_t i) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + if(i >= chart->point_cnt) return; + + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t scroll_left = lv_obj_get_scroll_left(obj); + + /*In shift mode the whole chart changes so the whole object*/ + if(chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT) { + lv_obj_invalidate(obj); + return; + } + + if(chart->type == LV_CHART_TYPE_LINE) { + lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + lv_coord_t x_ofs = obj->coords.x1 + pleft + bwidth - scroll_left; + lv_coord_t line_width = lv_obj_get_style_line_width(obj, LV_PART_ITEMS); + lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR); + + lv_area_t coords; + lv_area_copy(&coords, &obj->coords); + coords.y1 -= line_width + point_w; + coords.y2 += line_width + point_w; + + if(i < chart->point_cnt - 1) { + coords.x1 = ((w * i) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w; + coords.x2 = ((w * (i + 1)) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w; + lv_obj_invalidate_area(obj, &coords); + } + + if(i > 0) { + coords.x1 = ((w * (i - 1)) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w; + coords.x2 = ((w * i) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w; + lv_obj_invalidate_area(obj, &coords); + } + } + else if(chart->type == LV_CHART_TYPE_BAR) { + lv_area_t col_a; + int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/ + + lv_coord_t block_w = (w + block_gap) / chart->point_cnt; + + lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t x_act; + x_act = (int32_t)((int32_t)(block_w) * i) ; + x_act += obj->coords.x1 + bwidth + lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + + lv_obj_get_coords(obj, &col_a); + col_a.x1 = x_act - scroll_left; + col_a.x2 = col_a.x1 + block_w; + col_a.x1 -= block_gap; + + lv_obj_invalidate_area(obj, &col_a); + } + else { + lv_obj_invalidate(obj); + } +} + +static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a) +{ + if((*a) == NULL) return; + + lv_chart_t * chart = (lv_chart_t *) obj; + uint32_t point_cnt_old = chart->point_cnt; + uint32_t i; + + if(ser->start_point != 0) { + lv_coord_t * new_points = lv_mem_alloc(sizeof(lv_coord_t) * cnt); + LV_ASSERT_MALLOC(new_points); + if(new_points == NULL) return; + + if(cnt >= point_cnt_old) { + for(i = 0; i < point_cnt_old; i++) { + new_points[i] = + (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/ + } + for(i = point_cnt_old; i < cnt; i++) { + new_points[i] = LV_CHART_POINT_NONE; /*Fill up the rest with default value*/ + } + } + else { + for(i = 0; i < cnt; i++) { + new_points[i] = + (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/ + } + } + + /*Switch over pointer from old to new*/ + lv_mem_free((*a)); + (*a) = new_points; + } + else { + (*a) = lv_mem_realloc((*a), sizeof(lv_coord_t) * cnt); + LV_ASSERT_MALLOC((*a)); + if((*a) == NULL) return; + /*Initialize the new points*/ + if(cnt > point_cnt_old) { + for(i = point_cnt_old - 1; i < cnt; i++) { + (*a)[i] = LV_CHART_POINT_NONE; + } + } + } +} + +lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis) +{ + lv_chart_t * chart = (lv_chart_t *) obj; + switch(axis) { + case LV_CHART_AXIS_PRIMARY_Y: + return &chart->tick[0]; + case LV_CHART_AXIS_PRIMARY_X: + return &chart->tick[1]; + case LV_CHART_AXIS_SECONDARY_Y: + return &chart->tick[2]; + case LV_CHART_AXIS_SECONDARY_X: + return &chart->tick[3]; + default: + return NULL; + } +} + + +#endif diff --git a/lib/lvgl/src/extra/widgets/chart/lv_chart.h b/lib/lvgl/src/extra/widgets/chart/lv_chart.h new file mode 100644 index 00000000..394c0e7b --- /dev/null +++ b/lib/lvgl/src/extra/widgets/chart/lv_chart.h @@ -0,0 +1,460 @@ +/** + * @file lv_chart.h + * + */ + +#ifndef LV_CHART_H +#define LV_CHART_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_CHART != 0 + +/********************* + * DEFINES + *********************/ + +/**Default value of points. Can be used to not draw a point*/ +#if LV_USE_LARGE_COORD +#define LV_CHART_POINT_NONE (INT32_MAX) +#else +#define LV_CHART_POINT_NONE (INT16_MAX) +#endif +LV_EXPORT_CONST_INT(LV_CHART_POINT_NONE); + +/********************** + * TYPEDEFS + **********************/ + +/** + * Chart types + */ +enum { + LV_CHART_TYPE_NONE, /**< Don't draw the series*/ + LV_CHART_TYPE_LINE, /**< Connect the points with lines*/ + LV_CHART_TYPE_BAR, /**< Draw columns*/ + LV_CHART_TYPE_SCATTER, /**< Draw points and lines in 2D (x,y coordinates)*/ +}; +typedef uint8_t lv_chart_type_t; + +/** + * Chart update mode for `lv_chart_set_next` + */ +enum { + LV_CHART_UPDATE_MODE_SHIFT, /**< Shift old data to the left and add the new one the right*/ + LV_CHART_UPDATE_MODE_CIRCULAR, /**< Add the new data in a circular way*/ +}; +typedef uint8_t lv_chart_update_mode_t; + +/** + * Enumeration of the axis' + */ +enum { + LV_CHART_AXIS_PRIMARY_Y = 0x00, + LV_CHART_AXIS_SECONDARY_Y = 0x01, + LV_CHART_AXIS_PRIMARY_X = 0x02, + LV_CHART_AXIS_SECONDARY_X = 0x04, + _LV_CHART_AXIS_LAST +}; +typedef uint8_t lv_chart_axis_t; + +/** + * Descriptor a chart series + */ +typedef struct { + lv_coord_t * x_points; + lv_coord_t * y_points; + lv_color_t color; + uint16_t start_point; + uint8_t hidden : 1; + uint8_t x_ext_buf_assigned : 1; + uint8_t y_ext_buf_assigned : 1; + uint8_t x_axis_sec : 1; + uint8_t y_axis_sec : 1; +} lv_chart_series_t; + +typedef struct { + lv_point_t pos; + lv_coord_t point_id; + lv_color_t color; + lv_chart_series_t * ser; + lv_dir_t dir; + uint8_t pos_set: 1; /*1: pos is set; 0: point_id is set*/ +} lv_chart_cursor_t; + +typedef struct { + lv_coord_t major_len; + lv_coord_t minor_len; + lv_coord_t draw_size; + uint32_t minor_cnt : 15; + uint32_t major_cnt : 15; + uint32_t label_en : 1; +} lv_chart_tick_dsc_t; + + +typedef struct { + lv_obj_t obj; + lv_ll_t series_ll; /**< Linked list for the series (stores lv_chart_series_t)*/ + lv_ll_t cursor_ll; /**< Linked list for the cursors (stores lv_chart_cursor_t)*/ + lv_chart_tick_dsc_t tick[4]; + lv_coord_t ymin[2]; + lv_coord_t ymax[2]; + lv_coord_t xmin[2]; + lv_coord_t xmax[2]; + lv_coord_t pressed_point_id; + uint16_t hdiv_cnt; /**< Number of horizontal division lines*/ + uint16_t vdiv_cnt; /**< Number of vertical division lines*/ + uint16_t point_cnt; /**< Point number in a data line*/ + uint16_t zoom_x; + uint16_t zoom_y; + lv_chart_type_t type : 3; /**< Line or column chart*/ + lv_chart_update_mode_t update_mode : 1; +} lv_chart_t; + +extern const lv_obj_class_t lv_chart_class; + +/** + * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_chart_class` + * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END` + */ +typedef enum { + LV_CHART_DRAW_PART_DIV_LINE_INIT, /**< Used before/after drawn the div lines*/ + LV_CHART_DRAW_PART_DIV_LINE_HOR, /**< Used for each horizontal division lines*/ + LV_CHART_DRAW_PART_DIV_LINE_VER, /**< Used for each vertical division lines*/ + LV_CHART_DRAW_PART_LINE_AND_POINT, /**< Used on line and scatter charts for lines and points*/ + LV_CHART_DRAW_PART_BAR, /**< Used on bar charts for the rectangles*/ + LV_CHART_DRAW_PART_CURSOR, /**< Used on cursor lines and points*/ + LV_CHART_DRAW_PART_TICK_LABEL, /**< Used on tick lines and labels*/ +} lv_chart_draw_part_type_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a chart object + * @param parent pointer to an object, it will be the parent of the new chart + * @return pointer to the created chart + */ +lv_obj_t * lv_chart_create(lv_obj_t * parent); + +/** + * Set a new type for a chart + * @param obj pointer to a chart object + * @param type new type of the chart (from 'lv_chart_type_t' enum) + */ +void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type); +/** + * Set the number of points on a data line on a chart + * @param obj pointer to a chart object + * @param cnt new number of points on the data lines + */ +void lv_chart_set_point_count(lv_obj_t * obj, uint16_t cnt); + +/** + * Set the minimal and maximal y values on an axis + * @param obj pointer to a chart object + * @param axis `LV_CHART_AXIS_PRIMARY_Y` or `LV_CHART_AXIS_SECONDARY_Y` + * @param min minimum value of the y axis + * @param max maximum value of the y axis + */ +void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t min, lv_coord_t max); + +/** + * Set update mode of the chart object. Affects + * @param obj pointer to a chart object + * @param mode the update mode + */ +void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode); + +/** + * Set the number of horizontal and vertical division lines + * @param obj pointer to a chart object + * @param hdiv number of horizontal division lines + * @param vdiv number of vertical division lines + */ +void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv); + +/** + * Zoom into the chart in X direction + * @param obj pointer to a chart object + * @param zoom_x zoom in x direction. LV_ZOOM_NONE or 256 for no zoom, 512 double zoom + */ +void lv_chart_set_zoom_x(lv_obj_t * obj, uint16_t zoom_x); + +/** + * Zoom into the chart in Y direction + * @param obj pointer to a chart object + * @param zoom_y zoom in y direction. LV_ZOOM_NONE or 256 for no zoom, 512 double zoom + */ +void lv_chart_set_zoom_y(lv_obj_t * obj, uint16_t zoom_y); + +/** + * Get X zoom of a chart + * @param obj pointer to a chart object + * @return the X zoom value + */ +uint16_t lv_chart_get_zoom_x(const lv_obj_t * obj); + +/** + * Get Y zoom of a chart + * @param obj pointer to a chart object + * @return the Y zoom value + */ +uint16_t lv_chart_get_zoom_y(const lv_obj_t * obj); + +/** + * Set the number of tick lines on an axis + * @param obj pointer to a chart object + * @param axis an axis which ticks count should be set + * @param major_len length of major ticks + * @param minor_len length of minor ticks + * @param major_cnt number of major ticks on the axis + * @param minor_cnt number of minor ticks between two major ticks + * @param label_en true: enable label drawing on major ticks + * @param draw_size extra size required to draw the tick and labels + * (start with 20 px and increase if the ticks/labels are clipped) + */ +void lv_chart_set_axis_tick(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t major_len, lv_coord_t minor_len, + lv_coord_t major_cnt, lv_coord_t minor_cnt, bool label_en, lv_coord_t draw_size); + +/** + * Get the type of a chart + * @param obj pointer to chart object + * @return type of the chart (from 'lv_chart_t' enum) + */ +lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj); + +/** + * Get the data point number per data line on chart + * @param chart pointer to chart object + * @return point number on each data line + */ +uint16_t lv_chart_get_point_count(const lv_obj_t * obj); + +/** + * Get the current index of the x-axis start point in the data array + * @param chart pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @return the index of the current x start point in the data array + */ +uint16_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser); + +/** + * Get the position of a point to the chart. + * @param chart pointer to a chart object + * @param ser pointer to series + * @param id the index. + * @param p_out store the result position here + */ +void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_point_t * p_out); + +/** + * Refresh a chart if its data line has changed + * @param chart pointer to chart object + */ +void lv_chart_refresh(lv_obj_t * obj); + +/*====================== + * Series + *=====================*/ + +/** + * Allocate and add a data series to the chart + * @param obj pointer to a chart object + * @param color color of the data series + * @param axis the y axis to which the series should be attached (::LV_CHART_AXIS_PRIMARY_Y or ::LV_CHART_AXIS_SECONDARY_Y) + * @return pointer to the allocated data series + */ +lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis); + +/** + * Deallocate and remove a data series from a chart + * @param chart pointer to a chart object + * @param series pointer to a data series on 'chart' + */ +void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series); + +/** + * Hide/Unhide a single series of a chart. + * @param obj pointer to a chart object. + * @param series pointer to a series object + * @param hide true: hide the series + */ +void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide); + +/** + * Change the color of a series + * @param obj pointer to a chart object. + * @param series pointer to a series object + * @param color the new color of the series + */ +void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color); + +/** + * Set the index of the x-axis start point in the data array. + * This point will be considers the first (left) point and the other points will be drawn after it. + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @param id the index of the x point in the data array + */ +void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id); + +/** + * Get the next series. + * @param chart pointer to a chart + * @param ser the previous series or NULL to get the first + * @return the next series or NULL if there is no more. + */ +lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * chart, const lv_chart_series_t * ser); + + + +/*===================== + * Cursor + *====================*/ + +/** + * Add a cursor with a given color + * @param obj pointer to chart object + * @param color color of the cursor + * @param dir direction of the cursor. `LV_DIR_RIGHT/LEFT/TOP/DOWN/HOR/VER/ALL`. OR-ed values are possible + * @return pointer to the created cursor + */ +lv_chart_cursor_t * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir); + +/** + * Set the coordinate of the cursor with respect to the paddings + * @param obj pointer to a chart object + * @param cursor pointer to the cursor + * @param pos the new coordinate of cursor relative to the chart + */ +void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos); + +/** + * Stick the cursor to a point + * @param obj pointer to a chart object + * @param cursor pointer to the cursor + * @param ser pointer to a series + * @param point_id the point's index or `LV_CHART_POINT_NONE` to not assign to any points. + */ +void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, + uint16_t point_id); + +/** + * Get the coordinate of the cursor with respect to the paddings + * @param obj pointer to a chart object + * @param cursor pointer to cursor + * @return coordinate of the cursor as lv_point_t + */ +lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor); + +/*===================== + * Set/Get value(s) + *====================*/ + +/** + * Initialize all data points of a series with a value + * @param obj pointer to chart object + * @param ser pointer to a data series on 'chart' + * @param value the new value for all points. `LV_CHART_POINT_NONE` can be used to hide the points. + */ +void lv_chart_set_all_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value); + +/** + * Set the next point's Y value according to the update mode policy. + * @param obj pointer to chart object + * @param ser pointer to a data series on 'chart' + * @param value the new value of the next data + */ +void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value); + +/** + * Set the next point's X and Y value according to the update mode policy. + * @param obj pointer to chart object + * @param ser pointer to a data series on 'chart' + * @param x_value the new X value of the next data + * @param y_value the new Y value of the next data + */ +void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t x_value, lv_coord_t y_value); + +/** + * Set an individual point's y value of a chart's series directly based on its index + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @param id the index of the x point in the array + * @param value value to assign to array point + */ +void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t value); + +/** + * Set an individual point's x and y value of a chart's series directly based on its index + * Can be used only with `LV_CHART_TYPE_SCATTER`. + * @param obj pointer to chart object + * @param ser pointer to a data series on 'chart' + * @param id the index of the x point in the array + * @param x_value the new X value of the next data + * @param y_value the new Y value of the next data + */ +void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t x_value, + lv_coord_t y_value); + +/** + * Set an external array for the y data points to use for the chart + * NOTE: It is the users responsibility to make sure the `point_cnt` matches the external array size. + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @param array external array of points for chart + */ +void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[]); + +/** + * Set an external array for the x data points to use for the chart + * NOTE: It is the users responsibility to make sure the `point_cnt` matches the external array size. + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @param array external array of points for chart + */ +void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[]); + +/** + * Get the array of y values of a series + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @return the array of values with 'point_count' elements + */ +lv_coord_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser); + +/** + * Get the array of x values of a series + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @return the array of values with 'point_count' elements + */ +lv_coord_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser); + +/** + * Get the index of the currently pressed point. It's the same for every series. + * @param obj pointer to a chart object + * @return the index of the point [0 .. point count] or LV_CHART_POINT_ID_NONE if no point is being pressed + */ +uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_CHART*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_CHART_H*/ diff --git a/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.c b/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.c new file mode 100644 index 00000000..daf112e9 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.c @@ -0,0 +1,713 @@ +/** + * @file lv_colorwheel.c + * + * Based on the work of @AloyseTech and @paulpv. + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_colorwheel.h" +#if LV_USE_COLORWHEEL + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_colorwheel_class + +#define LV_CPICKER_DEF_QF 3 + +/** + * The OUTER_MASK_WIDTH define is required to assist with the placing of a mask over the outer ring of the widget as when the + * multicoloured radial lines are calculated for the outer ring of the widget their lengths are jittering because of the + * integer based arithmetic. From tests the maximum delta was found to be 2 so the current value is set to 3 to achieve + * appropriate masking. + */ +#define OUTER_MASK_WIDTH 3 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_colorwheel_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_colorwheel_event(const lv_obj_class_t * class_p, lv_event_t * e); + +static void draw_disc_grad(lv_event_t * e); +static void draw_knob(lv_event_t * e); +static void invalidate_knob(lv_obj_t * obj); +static lv_area_t get_knob_area(lv_obj_t * obj); + +static void next_color_mode(lv_obj_t * obj); +static lv_res_t double_click_reset(lv_obj_t * obj); +static void refr_knob_pos(lv_obj_t * obj); +static lv_color_t angle_to_mode_color_fast(lv_obj_t * obj, uint16_t angle); +static uint16_t get_angle(lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_colorwheel_class = {.instance_size = sizeof(lv_colorwheel_t), .base_class = &lv_obj_class, + .constructor_cb = lv_colorwheel_constructor, + .event_cb = lv_colorwheel_event, + .width_def = LV_DPI_DEF * 2, + .height_def = LV_DPI_DEF * 2, + .editable = LV_OBJ_CLASS_EDITABLE_TRUE, + }; + +static bool create_knob_recolor; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create a color_picker object + * @param parent pointer to an object, it will be the parent of the new color_picker + * @return pointer to the created color_picker + */ +lv_obj_t * lv_colorwheel_create(lv_obj_t * parent, bool knob_recolor) +{ + LV_LOG_INFO("begin"); + create_knob_recolor = knob_recolor; + + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the current hsv of a color wheel. + * @param colorwheel pointer to color wheel object + * @param color current selected hsv + * @return true if changed, otherwise false + */ +bool lv_colorwheel_set_hsv(lv_obj_t * obj, lv_color_hsv_t hsv) +{ + if(hsv.h > 360) hsv.h %= 360; + if(hsv.s > 100) hsv.s = 100; + if(hsv.v > 100) hsv.v = 100; + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + if(colorwheel->hsv.h == hsv.h && colorwheel->hsv.s == hsv.s && colorwheel->hsv.v == hsv.v) return false; + + colorwheel->hsv = hsv; + + refr_knob_pos(obj); + + lv_obj_invalidate(obj); + + return true; +} + +/** + * Set the current color of a color wheel. + * @param colorwheel pointer to color wheel object + * @param color current selected color + * @return true if changed, otherwise false + */ +bool lv_colorwheel_set_rgb(lv_obj_t * obj, lv_color_t color) +{ + lv_color32_t c32; + c32.full = lv_color_to32(color); + + return lv_colorwheel_set_hsv(obj, lv_color_rgb_to_hsv(c32.ch.red, c32.ch.green, c32.ch.blue)); +} + +/** + * Set the current color mode. + * @param colorwheel pointer to color wheel object + * @param mode color mode (hue/sat/val) + */ +void lv_colorwheel_set_mode(lv_obj_t * obj, lv_colorwheel_mode_t mode) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + colorwheel->mode = mode; + refr_knob_pos(obj); + lv_obj_invalidate(obj); +} + +/** + * Set if the color mode is changed on long press on center + * @param colorwheel pointer to color wheel object + * @param fixed color mode cannot be changed on long press + */ +void lv_colorwheel_set_mode_fixed(lv_obj_t * obj, bool fixed) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + colorwheel->mode_fixed = fixed; +} + +/*===================== + * Getter functions + *====================*/ + + +/** + * Get the current selected hsv of a color wheel. + * @param colorwheel pointer to color wheel object + * @return current selected hsv + */ +lv_color_hsv_t lv_colorwheel_get_hsv(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + return colorwheel->hsv; +} + +/** + * Get the current selected color of a color wheel. + * @param colorwheel pointer to color wheel object + * @return color current selected color + */ +lv_color_t lv_colorwheel_get_rgb(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + return lv_color_hsv_to_rgb(colorwheel->hsv.h, colorwheel->hsv.s, colorwheel->hsv.v); +} + +/** + * Get the current color mode. + * @param colorwheel pointer to color wheel object + * @return color mode (hue/sat/val) + */ +lv_colorwheel_mode_t lv_colorwheel_get_color_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + return colorwheel->mode; +} + +/** + * Get if the color mode is changed on long press on center + * @param colorwheel pointer to color wheel object + * @return mode cannot be changed on long press + */ +bool lv_colorwheel_get_color_mode_fixed(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + return colorwheel->mode_fixed; +} + +/*===================== + * Other functions + *====================*/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_colorwheel_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + colorwheel->hsv.h = 0; + colorwheel->hsv.s = 100; + colorwheel->hsv.v = 100; + colorwheel->mode = LV_COLORWHEEL_MODE_HUE; + colorwheel->mode_fixed = 0; + colorwheel->last_click_time = 0; + colorwheel->last_change_time = 0; + colorwheel->knob.recolor = create_knob_recolor; + + lv_obj_add_flag(obj, LV_OBJ_FLAG_ADV_HITTEST); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN); + refr_knob_pos(obj); +} + +static void draw_disc_grad(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + lv_coord_t w = lv_obj_get_width(obj); + lv_coord_t h = lv_obj_get_height(obj); + lv_coord_t cx = obj->coords.x1 + w / 2; + lv_coord_t cy = obj->coords.y1 + h / 2; + lv_coord_t r = w / 2; + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc); + + line_dsc.width = (r * 628 / (256 / LV_CPICKER_DEF_QF)) / 100; + line_dsc.width += 2; + uint16_t i; + uint32_t a = 0; + lv_coord_t cir_w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN); + +#if LV_DRAW_COMPLEX + /*Mask outer and inner ring of widget to tidy up ragged edges of lines while drawing outer ring*/ + lv_draw_mask_radius_param_t mask_out_param; + lv_draw_mask_radius_init(&mask_out_param, &obj->coords, LV_RADIUS_CIRCLE, false); + int16_t mask_out_id = lv_draw_mask_add(&mask_out_param, 0); + + lv_area_t mask_area; + lv_area_copy(&mask_area, &obj->coords); + mask_area.x1 += cir_w; + mask_area.x2 -= cir_w; + mask_area.y1 += cir_w; + mask_area.y2 -= cir_w; + lv_draw_mask_radius_param_t mask_in_param; + lv_draw_mask_radius_init(&mask_in_param, &mask_area, LV_RADIUS_CIRCLE, true); + int16_t mask_in_id = lv_draw_mask_add(&mask_in_param, 0); + + /*The inner and outer line ends will be masked out. + *So make lines a little bit longer because the masking makes a more even result*/ + lv_coord_t cir_w_extra = line_dsc.width; +#else + lv_coord_t cir_w_extra = 0; +#endif + + for(i = 0; i <= 256; i += LV_CPICKER_DEF_QF, a += 360 * LV_CPICKER_DEF_QF) { + line_dsc.color = angle_to_mode_color_fast(obj, i); + uint16_t angle_trigo = (uint16_t)(a >> 8); /*i * 360 / 256 is the scale to apply, but we can skip multiplication here*/ + + lv_point_t p[2]; + p[0].x = cx + ((r + cir_w_extra) * lv_trigo_sin(angle_trigo) >> LV_TRIGO_SHIFT); + p[0].y = cy + ((r + cir_w_extra) * lv_trigo_cos(angle_trigo) >> LV_TRIGO_SHIFT); + p[1].x = cx + ((r - cir_w - cir_w_extra) * lv_trigo_sin(angle_trigo) >> LV_TRIGO_SHIFT); + p[1].y = cy + ((r - cir_w - cir_w_extra) * lv_trigo_cos(angle_trigo) >> LV_TRIGO_SHIFT); + + lv_draw_line(draw_ctx, &line_dsc, &p[0], &p[1]); + } + +#if LV_DRAW_COMPLEX + lv_draw_mask_free_param(&mask_out_param); + lv_draw_mask_free_param(&mask_in_param); + lv_draw_mask_remove_id(mask_out_id); + lv_draw_mask_remove_id(mask_in_id); +#endif +} + +static void draw_knob(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + lv_draw_rect_dsc_t cir_dsc; + lv_draw_rect_dsc_init(&cir_dsc); + lv_obj_init_draw_rect_dsc(obj, LV_PART_KNOB, &cir_dsc); + + cir_dsc.radius = LV_RADIUS_CIRCLE; + + if(colorwheel->knob.recolor) { + cir_dsc.bg_color = lv_colorwheel_get_rgb(obj); + } + + lv_area_t knob_area = get_knob_area(obj); + + lv_draw_rect(draw_ctx, &cir_dsc, &knob_area); +} + +static void invalidate_knob(lv_obj_t * obj) +{ + lv_area_t knob_area = get_knob_area(obj); + + lv_obj_invalidate_area(obj, &knob_area); +} + +static lv_area_t get_knob_area(lv_obj_t * obj) +{ + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + /*Get knob's radius*/ + uint16_t r = 0; + r = lv_obj_get_style_arc_width(obj, LV_PART_MAIN) / 2; + + lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB); + lv_coord_t right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB); + lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB); + lv_coord_t bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB); + + lv_area_t knob_area; + knob_area.x1 = obj->coords.x1 + colorwheel->knob.pos.x - r - left; + knob_area.y1 = obj->coords.y1 + colorwheel->knob.pos.y - r - right; + knob_area.x2 = obj->coords.x1 + colorwheel->knob.pos.x + r + top; + knob_area.y2 = obj->coords.y1 + colorwheel->knob.pos.y + r + bottom; + + return knob_area; +} + +static void lv_colorwheel_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + /*Call the ancestor's event handler*/ + lv_res_t res = lv_obj_event_base(MY_CLASS, e); + + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) { + lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB); + lv_coord_t right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB); + lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB); + lv_coord_t bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB); + + lv_coord_t knob_pad = LV_MAX4(left, right, top, bottom) + 2; + lv_coord_t * s = lv_event_get_param(e); + *s = LV_MAX(*s, knob_pad); + } + else if(code == LV_EVENT_SIZE_CHANGED) { + void * param = lv_event_get_param(e); + /*Refresh extended draw area to make knob visible*/ + if(lv_obj_get_width(obj) != lv_area_get_width(param) || + lv_obj_get_height(obj) != lv_area_get_height(param)) { + refr_knob_pos(obj); + } + } + else if(code == LV_EVENT_STYLE_CHANGED) { + /*Refresh extended draw area to make knob visible*/ + refr_knob_pos(obj); + } + else if(code == LV_EVENT_KEY) { + uint32_t c = *((uint32_t *)lv_event_get_param(e)); /*uint32_t because can be UTF-8*/ + + if(c == LV_KEY_RIGHT || c == LV_KEY_UP) { + lv_color_hsv_t hsv_cur; + hsv_cur = colorwheel->hsv; + + switch(colorwheel->mode) { + case LV_COLORWHEEL_MODE_HUE: + hsv_cur.h = (colorwheel->hsv.h + 1) % 360; + break; + case LV_COLORWHEEL_MODE_SATURATION: + hsv_cur.s = (colorwheel->hsv.s + 1) % 100; + break; + case LV_COLORWHEEL_MODE_VALUE: + hsv_cur.v = (colorwheel->hsv.v + 1) % 100; + break; + } + + if(lv_colorwheel_set_hsv(obj, hsv_cur)) { + res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + if(res != LV_RES_OK) return; + } + } + else if(c == LV_KEY_LEFT || c == LV_KEY_DOWN) { + lv_color_hsv_t hsv_cur; + hsv_cur = colorwheel->hsv; + + switch(colorwheel->mode) { + case LV_COLORWHEEL_MODE_HUE: + hsv_cur.h = colorwheel->hsv.h > 0 ? (colorwheel->hsv.h - 1) : 360; + break; + case LV_COLORWHEEL_MODE_SATURATION: + hsv_cur.s = colorwheel->hsv.s > 0 ? (colorwheel->hsv.s - 1) : 100; + break; + case LV_COLORWHEEL_MODE_VALUE: + hsv_cur.v = colorwheel->hsv.v > 0 ? (colorwheel->hsv.v - 1) : 100; + break; + } + + if(lv_colorwheel_set_hsv(obj, hsv_cur)) { + res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + if(res != LV_RES_OK) return; + } + } + } + else if(code == LV_EVENT_PRESSED) { + colorwheel->last_change_time = lv_tick_get(); + lv_indev_get_point(lv_indev_get_act(), &colorwheel->last_press_point); + res = double_click_reset(obj); + if(res != LV_RES_OK) return; + } + else if(code == LV_EVENT_PRESSING) { + lv_indev_t * indev = lv_indev_get_act(); + if(indev == NULL) return; + + lv_indev_type_t indev_type = lv_indev_get_type(indev); + lv_point_t p; + if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) { + p.x = obj->coords.x1 + lv_obj_get_width(obj) / 2; + p.y = obj->coords.y1 + lv_obj_get_height(obj) / 2; + } + else { + lv_indev_get_point(indev, &p); + } + + lv_coord_t drag_limit = indev->driver->scroll_limit; + if((LV_ABS(p.x - colorwheel->last_press_point.x) > drag_limit) || + (LV_ABS(p.y - colorwheel->last_press_point.y) > drag_limit)) { + colorwheel->last_change_time = lv_tick_get(); + colorwheel->last_press_point.x = p.x; + colorwheel->last_press_point.y = p.y; + } + + p.x -= obj->coords.x1; + p.y -= obj->coords.y1; + + /*Ignore pressing in the inner area*/ + uint16_t w = lv_obj_get_width(obj); + + int16_t angle = 0; + lv_coord_t cir_w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN); + + lv_coord_t r_in = w / 2; + p.x -= r_in; + p.y -= r_in; + bool on_ring = true; + r_in -= cir_w; + if(r_in > LV_DPI_DEF / 2) { + lv_coord_t inner = cir_w / 2; + r_in -= inner; + + if(r_in < LV_DPI_DEF / 2) r_in = LV_DPI_DEF / 2; + } + + if(p.x * p.x + p.y * p.y < r_in * r_in) { + on_ring = false; + } + + /*If the inner area is being pressed, go to the next color mode on long press*/ + uint32_t diff = lv_tick_elaps(colorwheel->last_change_time); + if(!on_ring && diff > indev->driver->long_press_time && !colorwheel->mode_fixed) { + next_color_mode(obj); + lv_indev_wait_release(lv_indev_get_act()); + return; + } + + /*Set the angle only if pressed on the ring*/ + if(!on_ring) return; + + angle = lv_atan2(p.x, p.y) % 360; + + lv_color_hsv_t hsv_cur; + hsv_cur = colorwheel->hsv; + + switch(colorwheel->mode) { + case LV_COLORWHEEL_MODE_HUE: + hsv_cur.h = angle; + break; + case LV_COLORWHEEL_MODE_SATURATION: + hsv_cur.s = (angle * 100) / 360; + break; + case LV_COLORWHEEL_MODE_VALUE: + hsv_cur.v = (angle * 100) / 360; + break; + } + + if(lv_colorwheel_set_hsv(obj, hsv_cur)) { + res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + if(res != LV_RES_OK) return; + } + } + else if(code == LV_EVENT_HIT_TEST) { + lv_hit_test_info_t * info = lv_event_get_param(e);; + + /*Valid clicks can be only in the circle*/ + info->res = _lv_area_is_point_on(&obj->coords, info->point, LV_RADIUS_CIRCLE); + } + else if(code == LV_EVENT_DRAW_MAIN) { + draw_disc_grad(e); + draw_knob(e); + } + else if(code == LV_EVENT_COVER_CHECK) { + lv_cover_check_info_t * info = lv_event_get_param(e); + if(info->res != LV_COVER_RES_MASKED) info->res = LV_COVER_RES_NOT_COVER; + } +} + + + +static void next_color_mode(lv_obj_t * obj) +{ + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + colorwheel->mode = (colorwheel->mode + 1) % 3; + refr_knob_pos(obj); + lv_obj_invalidate(obj); +} + +static void refr_knob_pos(lv_obj_t * obj) +{ + invalidate_knob(obj); + + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + lv_coord_t w = lv_obj_get_width(obj); + + lv_coord_t scale_w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN); + lv_coord_t r = (w - scale_w) / 2; + uint16_t angle = get_angle(obj); + colorwheel->knob.pos.x = (((int32_t)r * lv_trigo_sin(angle)) >> LV_TRIGO_SHIFT); + colorwheel->knob.pos.y = (((int32_t)r * lv_trigo_cos(angle)) >> LV_TRIGO_SHIFT); + colorwheel->knob.pos.x = colorwheel->knob.pos.x + w / 2; + colorwheel->knob.pos.y = colorwheel->knob.pos.y + w / 2; + + invalidate_knob(obj); +} + +static lv_res_t double_click_reset(lv_obj_t * obj) +{ + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + lv_indev_t * indev = lv_indev_get_act(); + /*Double clicked? Use long press time as double click time out*/ + if(lv_tick_elaps(colorwheel->last_click_time) < indev->driver->long_press_time) { + lv_color_hsv_t hsv_cur; + hsv_cur = colorwheel->hsv; + + switch(colorwheel->mode) { + case LV_COLORWHEEL_MODE_HUE: + hsv_cur.h = 0; + break; + case LV_COLORWHEEL_MODE_SATURATION: + hsv_cur.s = 100; + break; + case LV_COLORWHEEL_MODE_VALUE: + hsv_cur.v = 100; + break; + } + + lv_indev_wait_release(indev); + + if(lv_colorwheel_set_hsv(obj, hsv_cur)) { + lv_res_t res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + if(res != LV_RES_OK) return res; + } + } + colorwheel->last_click_time = lv_tick_get(); + + return LV_RES_OK; +} + +#define SWAPPTR(A, B) do { uint8_t * t = A; A = B; B = t; } while(0) +#define HSV_PTR_SWAP(sextant,r,g,b) if((sextant) & 2) { SWAPPTR((r), (b)); } if((sextant) & 4) { SWAPPTR((g), (b)); } if(!((sextant) & 6)) { \ + if(!((sextant) & 1)) { SWAPPTR((r), (g)); } } else { if((sextant) & 1) { SWAPPTR((r), (g)); } } + +/** + * Based on the idea from https://www.vagrearg.org/content/hsvrgb + * Here we want to compute an approximate RGB value from a HSV input color space. We don't want to be accurate + * (for that, there's lv_color_hsv_to_rgb), but we want to be fast. + * + * Few tricks are used here: Hue is in range [0; 6 * 256] (so that the sextant is in the high byte and the fractional part is in the low byte) + * both s and v are in [0; 255] range (very convenient to avoid divisions). + * + * We fold all symmetry by swapping the R, G, B pointers so that the code is the same for all sextants. + * We replace division by 255 by a division by 256, a.k.a a shift right by 8 bits. + * This is wrong, but since this is only used to compute the pixels on the screen and not the final color, it's ok. + */ +static void fast_hsv2rgb(uint16_t h, uint8_t s, uint8_t v, uint8_t * r, uint8_t * g, uint8_t * b) +{ + if(!s) { + *r = *g = *b = v; + return; + } + + uint8_t sextant = h >> 8; + HSV_PTR_SWAP(sextant, r, g, b); /*Swap pointers so the conversion code is the same*/ + + *g = v; + + uint8_t bb = ~s; + uint16_t ww = v * bb; /*Don't try to be precise, but instead, be fast*/ + *b = ww >> 8; + + uint8_t h_frac = h & 0xff; + + if(!(sextant & 1)) { + /*Up slope*/ + ww = !h_frac ? ((uint16_t)s << 8) : (s * (uint8_t)(-h_frac)); /*Skip multiply if not required*/ + } + else { + /*Down slope*/ + ww = s * h_frac; + } + bb = ww >> 8; + bb = ~bb; + ww = v * bb; + *r = ww >> 8; +} + +static lv_color_t angle_to_mode_color_fast(lv_obj_t * obj, uint16_t angle) +{ + lv_colorwheel_t * ext = (lv_colorwheel_t *)obj; + uint8_t r = 0, g = 0, b = 0; + static uint16_t h = 0; + static uint8_t s = 0, v = 0, m = 255; + static uint16_t angle_saved = 0xffff; + + /*If the angle is different recalculate scaling*/ + if(angle_saved != angle) m = 255; + angle_saved = angle; + + switch(ext->mode) { + default: + case LV_COLORWHEEL_MODE_HUE: + /*Don't recompute costly scaling if it does not change*/ + if(m != ext->mode) { + s = (uint8_t)(((uint16_t)ext->hsv.s * 51) / 20); + v = (uint8_t)(((uint16_t)ext->hsv.v * 51) / 20); + m = ext->mode; + } + fast_hsv2rgb(angle * 6, s, v, &r, &g, + &b); /*A smart compiler will replace x * 6 by (x << 2) + (x << 1) if it's more efficient*/ + break; + case LV_COLORWHEEL_MODE_SATURATION: + /*Don't recompute costly scaling if it does not change*/ + if(m != ext->mode) { + h = (uint16_t)(((uint32_t)ext->hsv.h * 6 * 256) / 360); + v = (uint8_t)(((uint16_t)ext->hsv.v * 51) / 20); + m = ext->mode; + } + fast_hsv2rgb(h, angle, v, &r, &g, &b); + break; + case LV_COLORWHEEL_MODE_VALUE: + /*Don't recompute costly scaling if it does not change*/ + if(m != ext->mode) { + h = (uint16_t)(((uint32_t)ext->hsv.h * 6 * 256) / 360); + s = (uint8_t)(((uint16_t)ext->hsv.s * 51) / 20); + m = ext->mode; + } + fast_hsv2rgb(h, s, angle, &r, &g, &b); + break; + } + return lv_color_make(r, g, b); +} + +static uint16_t get_angle(lv_obj_t * obj) +{ + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + uint16_t angle; + switch(colorwheel->mode) { + default: + case LV_COLORWHEEL_MODE_HUE: + angle = colorwheel->hsv.h; + break; + case LV_COLORWHEEL_MODE_SATURATION: + angle = (colorwheel->hsv.s * 360) / 100; + break; + case LV_COLORWHEEL_MODE_VALUE: + angle = (colorwheel->hsv.v * 360) / 100 ; + break; + } + return angle; +} + +#endif /*LV_USE_COLORWHEEL*/ diff --git a/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.h b/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.h new file mode 100644 index 00000000..e9c9d92e --- /dev/null +++ b/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.h @@ -0,0 +1,142 @@ +/** + * @file lv_colorwheel.h + * + */ + +#ifndef LV_COLORWHEEL_H +#define LV_COLORWHEEL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_COLORWHEEL + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +enum { + LV_COLORWHEEL_MODE_HUE, + LV_COLORWHEEL_MODE_SATURATION, + LV_COLORWHEEL_MODE_VALUE +}; +typedef uint8_t lv_colorwheel_mode_t; + + +/*Data of color picker*/ +typedef struct { + lv_obj_t obj; + lv_color_hsv_t hsv; + struct { + lv_point_t pos; + uint8_t recolor : 1; + } knob; + uint32_t last_click_time; + uint32_t last_change_time; + lv_point_t last_press_point; + lv_colorwheel_mode_t mode : 2; + uint8_t mode_fixed : 1; +} lv_colorwheel_t; + +extern const lv_obj_class_t lv_colorwheel_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a color picker object with disc shape + * @param parent pointer to an object, it will be the parent of the new color picker + * @param knob_recolor true: set the knob's color to the current color + * @return pointer to the created color picker + */ +lv_obj_t * lv_colorwheel_create(lv_obj_t * parent, bool knob_recolor); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the current hsv of a color wheel. + * @param colorwheel pointer to color wheel object + * @param color current selected hsv + * @return true if changed, otherwise false + */ +bool lv_colorwheel_set_hsv(lv_obj_t * obj, lv_color_hsv_t hsv); + +/** + * Set the current color of a color wheel. + * @param colorwheel pointer to color wheel object + * @param color current selected color + * @return true if changed, otherwise false + */ +bool lv_colorwheel_set_rgb(lv_obj_t * obj, lv_color_t color); + +/** + * Set the current color mode. + * @param colorwheel pointer to color wheel object + * @param mode color mode (hue/sat/val) + */ +void lv_colorwheel_set_mode(lv_obj_t * obj, lv_colorwheel_mode_t mode); + +/** + * Set if the color mode is changed on long press on center + * @param colorwheel pointer to color wheel object + * @param fixed color mode cannot be changed on long press + */ +void lv_colorwheel_set_mode_fixed(lv_obj_t * obj, bool fixed); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get the current selected hsv of a color wheel. + * @param colorwheel pointer to color wheel object + * @return current selected hsv + */ +lv_color_hsv_t lv_colorwheel_get_hsv(lv_obj_t * obj); + +/** + * Get the current selected color of a color wheel. + * @param colorwheel pointer to color wheel object + * @return color current selected color + */ +lv_color_t lv_colorwheel_get_rgb(lv_obj_t * obj); + +/** + * Get the current color mode. + * @param colorwheel pointer to color wheel object + * @return color mode (hue/sat/val) + */ +lv_colorwheel_mode_t lv_colorwheel_get_color_mode(lv_obj_t * obj); + +/** + * Get if the color mode is changed on long press on center + * @param colorwheel pointer to color wheel object + * @return mode cannot be changed on long press + */ +bool lv_colorwheel_get_color_mode_fixed(lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_COLORWHEEL*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_COLORWHEEL_H*/ + diff --git a/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.c b/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.c new file mode 100644 index 00000000..00c3011c --- /dev/null +++ b/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.c @@ -0,0 +1,377 @@ +/** + * @file lv_imgbtn.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_imgbtn.h" + +#if LV_USE_IMGBTN != 0 + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_imgbtn_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_imgbtn_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void draw_main(lv_event_t * e); +static void lv_imgbtn_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void refr_img(lv_obj_t * imgbtn); +static lv_imgbtn_state_t suggest_state(lv_obj_t * imgbtn, lv_imgbtn_state_t state); +lv_imgbtn_state_t get_state(const lv_obj_t * imgbtn); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_imgbtn_class = { + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_imgbtn_t), + .constructor_cb = lv_imgbtn_constructor, + .event_cb = lv_imgbtn_event, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create an image button object + * @param parent pointer to an object, it will be the parent of the new image button + * @return pointer to the created image button + */ +lv_obj_t * lv_imgbtn_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Set images for a state of the image button + * @param obj pointer to an image button object + * @param state for which state set the new image + * @param src_left pointer to an image source for the left side of the button (a C array or path to + * a file) + * @param src_mid pointer to an image source for the middle of the button (ideally 1px wide) (a C + * array or path to a file) + * @param src_right pointer to an image source for the right side of the button (a C array or path + * to a file) + */ +void lv_imgbtn_set_src(lv_obj_t * obj, lv_imgbtn_state_t state, const void * src_left, const void * src_mid, + const void * src_right) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + + imgbtn->img_src_left[state] = src_left; + imgbtn->img_src_mid[state] = src_mid; + imgbtn->img_src_right[state] = src_right; + + refr_img(obj); +} + +void lv_imgbtn_set_state(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_state_t obj_state = LV_STATE_DEFAULT; + if(state == LV_IMGBTN_STATE_PRESSED || state == LV_IMGBTN_STATE_CHECKED_PRESSED) obj_state |= LV_STATE_PRESSED; + if(state == LV_IMGBTN_STATE_DISABLED || state == LV_IMGBTN_STATE_CHECKED_DISABLED) obj_state |= LV_STATE_DISABLED; + if(state == LV_IMGBTN_STATE_CHECKED_DISABLED || state == LV_IMGBTN_STATE_CHECKED_PRESSED || + state == LV_IMGBTN_STATE_CHECKED_RELEASED) { + obj_state |= LV_STATE_CHECKED; + } + + lv_obj_clear_state(obj, LV_STATE_CHECKED | LV_STATE_PRESSED | LV_STATE_DISABLED); + lv_obj_add_state(obj, obj_state); + + refr_img(obj); +} + +/*===================== + * Getter functions + *====================*/ + + +/** + * Get the left image in a given state + * @param obj pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the left image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_left(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + + return imgbtn->img_src_left[state]; +} + +/** + * Get the middle image in a given state + * @param obj pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the middle image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_middle(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + + return imgbtn->img_src_mid[state]; +} + +/** + * Get the right image in a given state + * @param obj pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the left image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_right(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + + return imgbtn->img_src_right[state]; +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_imgbtn_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + /*Initialize the allocated 'ext'*/ + lv_memset_00((void *)imgbtn->img_src_mid, sizeof(imgbtn->img_src_mid)); + lv_memset_00(imgbtn->img_src_left, sizeof(imgbtn->img_src_left)); + lv_memset_00(imgbtn->img_src_right, sizeof(imgbtn->img_src_right)); + + imgbtn->act_cf = LV_IMG_CF_UNKNOWN; +} + + +static void lv_imgbtn_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + lv_res_t res = lv_obj_event_base(&lv_imgbtn_class, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + if(code == LV_EVENT_PRESSED || code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) { + refr_img(obj); + } + else if(code == LV_EVENT_DRAW_MAIN) { + draw_main(e); + } + else if(code == LV_EVENT_COVER_CHECK) { + lv_cover_check_info_t * info = lv_event_get_param(e); + if(info->res != LV_COVER_RES_MASKED) info->res = LV_COVER_RES_NOT_COVER; + } + else if(code == LV_EVENT_GET_SELF_SIZE) { + lv_point_t * p = lv_event_get_self_size_info(e); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + lv_imgbtn_state_t state = suggest_state(obj, get_state(obj)); + if(imgbtn->img_src_left[state] == NULL && + imgbtn->img_src_mid[state] != NULL && + imgbtn->img_src_right[state] == NULL) { + lv_img_header_t header; + lv_img_decoder_get_info(imgbtn->img_src_mid[state], &header); + p->x = LV_MAX(p->x, header.w); + } + } +} + +static void draw_main(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + + /*Just draw_main an image*/ + lv_imgbtn_state_t state = suggest_state(obj, get_state(obj)); + + /*Simply draw the middle src if no tiled*/ + const void * src = imgbtn->img_src_left[state]; + + lv_coord_t tw = lv_obj_get_style_transform_width(obj, LV_PART_MAIN); + lv_coord_t th = lv_obj_get_style_transform_height(obj, LV_PART_MAIN); + lv_area_t coords; + lv_area_copy(&coords, &obj->coords); + coords.x1 -= tw; + coords.x2 += tw; + coords.y1 -= th; + coords.y2 += th; + + lv_draw_img_dsc_t img_dsc; + lv_draw_img_dsc_init(&img_dsc); + lv_obj_init_draw_img_dsc(obj, LV_PART_MAIN, &img_dsc); + + lv_img_header_t header; + lv_area_t coords_part; + lv_coord_t left_w = 0; + lv_coord_t right_w = 0; + + if(src) { + lv_img_decoder_get_info(src, &header); + left_w = header.w; + coords_part.x1 = coords.x1; + coords_part.y1 = coords.y1; + coords_part.x2 = coords.x1 + header.w - 1; + coords_part.y2 = coords.y1 + header.h - 1; + lv_draw_img(draw_ctx, &img_dsc, &coords_part, src); + } + + src = imgbtn->img_src_right[state]; + if(src) { + lv_img_decoder_get_info(src, &header); + right_w = header.w; + coords_part.x1 = coords.x2 - header.w + 1; + coords_part.y1 = coords.y1; + coords_part.x2 = coords.x2; + coords_part.y2 = coords.y1 + header.h - 1; + lv_draw_img(draw_ctx, &img_dsc, &coords_part, src); + } + + src = imgbtn->img_src_mid[state]; + if(src) { + lv_area_t clip_area_center; + clip_area_center.x1 = coords.x1 + left_w; + clip_area_center.x2 = coords.x2 - right_w; + clip_area_center.y1 = coords.y1; + clip_area_center.y2 = coords.y2; + + + bool comm_res; + comm_res = _lv_area_intersect(&clip_area_center, &clip_area_center, draw_ctx->clip_area); + if(comm_res) { + lv_coord_t i; + lv_img_decoder_get_info(src, &header); + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area_center; + + coords_part.x1 = coords.x1 + left_w; + coords_part.y1 = coords.y1; + coords_part.x2 = coords_part.x1 + header.w - 1; + coords_part.y2 = coords_part.y1 + header.h - 1; + + for(i = coords_part.x1; i < (lv_coord_t)(clip_area_center.x2 + header.w - 1); i += header.w) { + lv_draw_img(draw_ctx, &img_dsc, &coords_part, src); + coords_part.x1 = coords_part.x2 + 1; + coords_part.x2 += header.w; + } + draw_ctx->clip_area = clip_area_ori; + } + } +} + +static void refr_img(lv_obj_t * obj) +{ + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + lv_imgbtn_state_t state = suggest_state(obj, get_state(obj)); + lv_img_header_t header; + + const void * src = imgbtn->img_src_mid[state]; + if(src == NULL) return; + + lv_res_t info_res = LV_RES_OK; + info_res = lv_img_decoder_get_info(src, &header); + + if(info_res == LV_RES_OK) { + imgbtn->act_cf = header.cf; + lv_obj_refresh_self_size(obj); + lv_obj_set_height(obj, header.h); /*Keep the user defined width*/ + } + else { + imgbtn->act_cf = LV_IMG_CF_UNKNOWN; + } + + lv_obj_invalidate(obj); +} + +/** + * If `src` is not defined for the current state try to get a state which is related to the current but has `src`. + * E.g. if the PRESSED src is not set but the RELEASED does, use the RELEASED. + * @param imgbtn pointer to an image button + * @param state the state to convert + * @return the suggested state + */ +static lv_imgbtn_state_t suggest_state(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + if(imgbtn->img_src_mid[state] == NULL) { + switch(state) { + case LV_IMGBTN_STATE_PRESSED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + case LV_IMGBTN_STATE_CHECKED_RELEASED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + case LV_IMGBTN_STATE_CHECKED_PRESSED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_CHECKED_RELEASED]) return LV_IMGBTN_STATE_CHECKED_RELEASED; + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_PRESSED]) return LV_IMGBTN_STATE_PRESSED; + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + case LV_IMGBTN_STATE_DISABLED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + case LV_IMGBTN_STATE_CHECKED_DISABLED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_CHECKED_RELEASED]) return LV_IMGBTN_STATE_CHECKED_RELEASED; + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + default: + break; + } + } + + return state; +} + +lv_imgbtn_state_t get_state(const lv_obj_t * imgbtn) +{ + LV_ASSERT_OBJ(imgbtn, MY_CLASS); + + lv_state_t obj_state = lv_obj_get_state(imgbtn); + + if(obj_state & LV_STATE_DISABLED) { + if(obj_state & LV_STATE_CHECKED) return LV_IMGBTN_STATE_CHECKED_DISABLED; + else return LV_IMGBTN_STATE_DISABLED; + } + + if(obj_state & LV_STATE_CHECKED) { + if(obj_state & LV_STATE_PRESSED) return LV_IMGBTN_STATE_CHECKED_PRESSED; + else return LV_IMGBTN_STATE_CHECKED_RELEASED; + } + else { + if(obj_state & LV_STATE_PRESSED) return LV_IMGBTN_STATE_PRESSED; + else return LV_IMGBTN_STATE_RELEASED; + } +} + +#endif diff --git a/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.h b/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.h new file mode 100644 index 00000000..597faea1 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.h @@ -0,0 +1,131 @@ +/** + * @file lv_imgbtn.h + * + */ + +#ifndef LV_IMGBTN_H +#define LV_IMGBTN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_IMGBTN != 0 + +/********************* + * DEFINES + *********************/ +typedef enum { + LV_IMGBTN_STATE_RELEASED, + LV_IMGBTN_STATE_PRESSED, + LV_IMGBTN_STATE_DISABLED, + LV_IMGBTN_STATE_CHECKED_RELEASED, + LV_IMGBTN_STATE_CHECKED_PRESSED, + LV_IMGBTN_STATE_CHECKED_DISABLED, + _LV_IMGBTN_STATE_NUM, +} lv_imgbtn_state_t; + +/********************** + * TYPEDEFS + **********************/ +/*Data of image button*/ +typedef struct { + lv_obj_t obj; + const void * img_src_mid[_LV_IMGBTN_STATE_NUM]; /*Store center images to each state*/ + const void * img_src_left[_LV_IMGBTN_STATE_NUM]; /*Store left side images to each state*/ + const void * img_src_right[_LV_IMGBTN_STATE_NUM]; /*Store right side images to each state*/ + lv_img_cf_t act_cf; /*Color format of the currently active image*/ +} lv_imgbtn_t; + +extern const lv_obj_class_t lv_imgbtn_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create an image button object + * @param parent pointer to an object, it will be the parent of the new image button + * @return pointer to the created image button + */ +lv_obj_t * lv_imgbtn_create(lv_obj_t * parent); + +/*====================== + * Add/remove functions + *=====================*/ + +/*===================== + * Setter functions + *====================*/ + +/** + * Set images for a state of the image button + * @param imgbtn pointer to an image button object + * @param state for which state set the new image + * @param src_left pointer to an image source for the left side of the button (a C array or path to + * a file) + * @param src_mid pointer to an image source for the middle of the button (ideally 1px wide) (a C + * array or path to a file) + * @param src_right pointer to an image source for the right side of the button (a C array or path + * to a file) + */ +void lv_imgbtn_set_src(lv_obj_t * imgbtn, lv_imgbtn_state_t state, const void * src_left, const void * src_mid, + const void * src_right); + + +/** + * Use this function instead of `lv_obj_add/clear_state` to set a state manually + * @param imgbtn pointer to an image button object + * @param state the new state + */ +void lv_imgbtn_set_state(lv_obj_t * imgbtn, lv_imgbtn_state_t state); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get the left image in a given state + * @param imgbtn pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the left image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_left(lv_obj_t * imgbtn, lv_imgbtn_state_t state); + +/** + * Get the middle image in a given state + * @param imgbtn pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the middle image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_middle(lv_obj_t * imgbtn, lv_imgbtn_state_t state); + +/** + * Get the right image in a given state + * @param imgbtn pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the left image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_right(lv_obj_t * imgbtn, lv_imgbtn_state_t state); + + +/*===================== + * Other functions + *====================*/ + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_IMGBTN*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_IMGBTN_H*/ diff --git a/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.c b/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.c new file mode 100644 index 00000000..8e052e33 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.c @@ -0,0 +1,430 @@ + +/** + * @file lv_keyboard.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_keyboard.h" +#if LV_USE_KEYBOARD + +#include "../../../widgets/lv_textarea.h" +#include "../../../misc/lv_assert.h" + +#include + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_keyboard_class +#define LV_KB_BTN(width) LV_BTNMATRIX_CTRL_POPOVER | width + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_keyboard_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +static void lv_keyboard_update_map(lv_obj_t * obj); + +static void lv_keyboard_update_ctrl_map(lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_keyboard_class = { + .constructor_cb = lv_keyboard_constructor, + .width_def = LV_PCT(100), + .height_def = LV_PCT(50), + .instance_size = sizeof(lv_keyboard_t), + .editable = 1, + .base_class = &lv_btnmatrix_class +}; + +static const char * const default_kb_map_lc[] = {"1#", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", LV_SYMBOL_BACKSPACE, "\n", + "ABC", "a", "s", "d", "f", "g", "h", "j", "k", "l", LV_SYMBOL_NEW_LINE, "\n", + "_", "-", "z", "x", "c", "v", "b", "n", "m", ".", ",", ":", "\n", + LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" + }; + +static const lv_btnmatrix_ctrl_t default_kb_ctrl_lc_map[] = { + LV_KEYBOARD_CTRL_BTN_FLAGS | 5, LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_BTNMATRIX_CTRL_CHECKED | 7, + LV_KEYBOARD_CTRL_BTN_FLAGS | 6, LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_BTNMATRIX_CTRL_CHECKED | 7, + LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), + LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 +}; + +static const char * const default_kb_map_uc[] = {"1#", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", LV_SYMBOL_BACKSPACE, "\n", + "abc", "A", "S", "D", "F", "G", "H", "J", "K", "L", LV_SYMBOL_NEW_LINE, "\n", + "_", "-", "Z", "X", "C", "V", "B", "N", "M", ".", ",", ":", "\n", + LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" + }; + +static const lv_btnmatrix_ctrl_t default_kb_ctrl_uc_map[] = { + LV_KEYBOARD_CTRL_BTN_FLAGS | 5, LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_BTNMATRIX_CTRL_CHECKED | 7, + LV_KEYBOARD_CTRL_BTN_FLAGS | 6, LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_BTNMATRIX_CTRL_CHECKED | 7, + LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), + LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 +}; + +static const char * const default_kb_map_spec[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", LV_SYMBOL_BACKSPACE, "\n", + "abc", "+", "-", "/", "*", "=", "%", "!", "?", "#", "<", ">", "\n", + "\\", "@", "$", "(", ")", "{", "}", "[", "]", ";", "\"", "'", "\n", + LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" + }; + +static const lv_btnmatrix_ctrl_t default_kb_ctrl_spec_map[] = { + LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | 2, + LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), + LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), + LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 +}; + +static const char * const default_kb_map_num[] = {"1", "2", "3", LV_SYMBOL_KEYBOARD, "\n", + "4", "5", "6", LV_SYMBOL_OK, "\n", + "7", "8", "9", LV_SYMBOL_BACKSPACE, "\n", + "+/-", "0", ".", LV_SYMBOL_LEFT, LV_SYMBOL_RIGHT, "" + }; + +static const lv_btnmatrix_ctrl_t default_kb_ctrl_num_map[] = { + 1, 1, 1, LV_KEYBOARD_CTRL_BTN_FLAGS | 2, + 1, 1, 1, LV_KEYBOARD_CTRL_BTN_FLAGS | 2, + 1, 1, 1, 2, + 1, 1, 1, 1, 1 +}; + +static const char * * kb_map[9] = { + (const char * *)default_kb_map_lc, + (const char * *)default_kb_map_uc, + (const char * *)default_kb_map_spec, + (const char * *)default_kb_map_num, + (const char * *)default_kb_map_lc, + (const char * *)default_kb_map_lc, + (const char * *)default_kb_map_lc, + (const char * *)default_kb_map_lc, + (const char * *)NULL, +}; +static const lv_btnmatrix_ctrl_t * kb_ctrl[9] = { + default_kb_ctrl_lc_map, + default_kb_ctrl_uc_map, + default_kb_ctrl_spec_map, + default_kb_ctrl_num_map, + default_kb_ctrl_lc_map, + default_kb_ctrl_lc_map, + default_kb_ctrl_lc_map, + default_kb_ctrl_lc_map, + NULL, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create a Keyboard object + * @param parent pointer to an object, it will be the parent of the new keyboard + * @return pointer to the created keyboard + */ +lv_obj_t * lv_keyboard_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_keyboard_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Assign a Text Area to the Keyboard. The pressed characters will be put there. + * @param kb pointer to a Keyboard object + * @param ta pointer to a Text Area object to write there + */ +void lv_keyboard_set_textarea(lv_obj_t * obj, lv_obj_t * ta) +{ + if(ta) { + LV_ASSERT_OBJ(ta, &lv_textarea_class); + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + + /*Hide the cursor of the old Text area if cursor management is enabled*/ + if(keyboard->ta) { + lv_obj_clear_state(obj, LV_STATE_FOCUSED); + } + + keyboard->ta = ta; + + /*Show the cursor of the new Text area if cursor management is enabled*/ + if(keyboard->ta) { + lv_obj_add_flag(obj, LV_STATE_FOCUSED); + } +} + +/** + * Set a new a mode (text or number map) + * @param kb pointer to a Keyboard object + * @param mode the mode from 'lv_keyboard_mode_t' + */ +void lv_keyboard_set_mode(lv_obj_t * obj, lv_keyboard_mode_t mode) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + if(keyboard->mode == mode) return; + + keyboard->mode = mode; + lv_keyboard_update_map(obj); +} + +/** + * Show the button title in a popover when pressed. + * @param kb pointer to a Keyboard object + * @param en whether "popovers" mode is enabled + */ +void lv_keyboard_set_popovers(lv_obj_t * obj, bool en) +{ + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + + if(keyboard->popovers == en) { + return; + } + + keyboard->popovers = en; + lv_keyboard_update_ctrl_map(obj); +} + +/** + * Set a new map for the keyboard + * @param kb pointer to a Keyboard object + * @param mode keyboard map to alter 'lv_keyboard_mode_t' + * @param map pointer to a string array to describe the map. + * See 'lv_btnmatrix_set_map()' for more info. + */ +void lv_keyboard_set_map(lv_obj_t * obj, lv_keyboard_mode_t mode, const char * map[], + const lv_btnmatrix_ctrl_t ctrl_map[]) +{ + kb_map[mode] = map; + kb_ctrl[mode] = ctrl_map; + lv_keyboard_update_map(obj); +} + +/*===================== + * Getter functions + *====================*/ + +/** + * Assign a Text Area to the Keyboard. The pressed characters will be put there. + * @param kb pointer to a Keyboard object + * @return pointer to the assigned Text Area object + */ +lv_obj_t * lv_keyboard_get_textarea(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + return keyboard->ta; +} + +/** + * Set a new a mode (text or number map) + * @param kb pointer to a Keyboard object + * @return the current mode from 'lv_keyboard_mode_t' + */ +lv_keyboard_mode_t lv_keyboard_get_mode(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + return keyboard->mode; +} + +/** + * Tell whether "popovers" mode is enabled or not. + * @param kb pointer to a Keyboard object + * @return true: "popovers" mode is enabled; false: disabled + */ +bool lv_btnmatrix_get_popovers(const lv_obj_t * obj) +{ + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + return keyboard->popovers; +} + +/*===================== + * Other functions + *====================*/ + +/** + * Default keyboard event to add characters to the Text area and change the map. + * If a custom `event_cb` is added to the keyboard this function can be called from it to handle the + * button clicks + * @param kb pointer to a keyboard + * @param event the triggering event + */ +void lv_keyboard_def_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + uint16_t btn_id = lv_btnmatrix_get_selected_btn(obj); + if(btn_id == LV_BTNMATRIX_BTN_NONE) return; + + const char * txt = lv_btnmatrix_get_btn_text(obj, lv_btnmatrix_get_selected_btn(obj)); + if(txt == NULL) return; + + if(strcmp(txt, "abc") == 0) { + keyboard->mode = LV_KEYBOARD_MODE_TEXT_LOWER; + lv_btnmatrix_set_map(obj, kb_map[LV_KEYBOARD_MODE_TEXT_LOWER]); + lv_keyboard_update_ctrl_map(obj); + return; + } + else if(strcmp(txt, "ABC") == 0) { + keyboard->mode = LV_KEYBOARD_MODE_TEXT_UPPER; + lv_btnmatrix_set_map(obj, kb_map[LV_KEYBOARD_MODE_TEXT_UPPER]); + lv_keyboard_update_ctrl_map(obj); + return; + } + else if(strcmp(txt, "1#") == 0) { + keyboard->mode = LV_KEYBOARD_MODE_SPECIAL; + lv_btnmatrix_set_map(obj, kb_map[LV_KEYBOARD_MODE_SPECIAL]); + lv_keyboard_update_ctrl_map(obj); + return; + } + else if(strcmp(txt, LV_SYMBOL_CLOSE) == 0 || strcmp(txt, LV_SYMBOL_KEYBOARD) == 0) { + lv_res_t res = lv_event_send(obj, LV_EVENT_CANCEL, NULL); + if(res != LV_RES_OK) return; + + if(keyboard->ta) { + res = lv_event_send(keyboard->ta, LV_EVENT_CANCEL, NULL); + if(res != LV_RES_OK) return; + } + return; + } + else if(strcmp(txt, LV_SYMBOL_OK) == 0) { + lv_res_t res = lv_event_send(obj, LV_EVENT_READY, NULL); + if(res != LV_RES_OK) return; + + if(keyboard->ta) { + res = lv_event_send(keyboard->ta, LV_EVENT_READY, NULL); + if(res != LV_RES_OK) return; + } + return; + } + + /*Add the characters to the text area if set*/ + if(keyboard->ta == NULL) return; + + if(strcmp(txt, "Enter") == 0 || strcmp(txt, LV_SYMBOL_NEW_LINE) == 0) { + lv_textarea_add_char(keyboard->ta, '\n'); + if(lv_textarea_get_one_line(keyboard->ta)) { + lv_res_t res = lv_event_send(keyboard->ta, LV_EVENT_READY, NULL); + if(res != LV_RES_OK) return; + } + } + else if(strcmp(txt, LV_SYMBOL_LEFT) == 0) { + lv_textarea_cursor_left(keyboard->ta); + } + else if(strcmp(txt, LV_SYMBOL_RIGHT) == 0) { + lv_textarea_cursor_right(keyboard->ta); + } + else if(strcmp(txt, LV_SYMBOL_BACKSPACE) == 0) { + lv_textarea_del_char(keyboard->ta); + } + else if(strcmp(txt, "+/-") == 0) { + uint16_t cur = lv_textarea_get_cursor_pos(keyboard->ta); + const char * ta_txt = lv_textarea_get_text(keyboard->ta); + if(ta_txt[0] == '-') { + lv_textarea_set_cursor_pos(keyboard->ta, 1); + lv_textarea_del_char(keyboard->ta); + lv_textarea_add_char(keyboard->ta, '+'); + lv_textarea_set_cursor_pos(keyboard->ta, cur); + } + else if(ta_txt[0] == '+') { + lv_textarea_set_cursor_pos(keyboard->ta, 1); + lv_textarea_del_char(keyboard->ta); + lv_textarea_add_char(keyboard->ta, '-'); + lv_textarea_set_cursor_pos(keyboard->ta, cur); + } + else { + lv_textarea_set_cursor_pos(keyboard->ta, 0); + lv_textarea_add_char(keyboard->ta, '-'); + lv_textarea_set_cursor_pos(keyboard->ta, cur + 1); + } + } + else { + lv_textarea_add_text(keyboard->ta, txt); + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_keyboard_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE); + + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + keyboard->ta = NULL; + keyboard->mode = LV_KEYBOARD_MODE_TEXT_LOWER; + keyboard->popovers = 0; + + lv_obj_align(obj, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_add_event_cb(obj, lv_keyboard_def_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_set_style_base_dir(obj, LV_BASE_DIR_LTR, 0); + + lv_keyboard_update_map(obj); +} + +/** + * Update the key and control map for the current mode + * @param obj pointer to a keyboard object + */ +static void lv_keyboard_update_map(lv_obj_t * obj) +{ + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + lv_btnmatrix_set_map(obj, kb_map[keyboard->mode]); + lv_keyboard_update_ctrl_map(obj); +} + +/** + * Update the control map for the current mode + * @param obj pointer to a keyboard object + */ +static void lv_keyboard_update_ctrl_map(lv_obj_t * obj) +{ + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + + if(keyboard->popovers) { + /*Apply the current control map (already includes LV_BTNMATRIX_CTRL_POPOVER flags)*/ + lv_btnmatrix_set_ctrl_map(obj, kb_ctrl[keyboard->mode]); + } + else { + /*Make a copy of the current control map*/ + lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj; + lv_btnmatrix_ctrl_t * ctrl_map = lv_mem_alloc(btnm->btn_cnt * sizeof(lv_btnmatrix_ctrl_t)); + lv_memcpy(ctrl_map, kb_ctrl[keyboard->mode], sizeof(lv_btnmatrix_ctrl_t) * btnm->btn_cnt); + + /*Remove all LV_BTNMATRIX_CTRL_POPOVER flags*/ + for(uint16_t i = 0; i < btnm->btn_cnt; i++) { + ctrl_map[i] &= (~LV_BTNMATRIX_CTRL_POPOVER); + } + + /*Apply new control map and clean up*/ + lv_btnmatrix_set_ctrl_map(obj, ctrl_map); + lv_mem_free(ctrl_map); + } +} + +#endif /*LV_USE_KEYBOARD*/ diff --git a/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.h b/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.h new file mode 100644 index 00000000..7f65cd75 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.h @@ -0,0 +1,187 @@ +/** + * @file lv_keyboard.h + * + */ + +#ifndef LV_KEYBOARD_H +#define LV_KEYBOARD_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../widgets/lv_btnmatrix.h" + +#if LV_USE_KEYBOARD + +/*Testing of dependencies*/ +#if LV_USE_BTNMATRIX == 0 +#error "lv_kb: lv_btnm is required. Enable it in lv_conf.h (LV_USE_BTNMATRIX 1) " +#endif + +#if LV_USE_TEXTAREA == 0 +#error "lv_kb: lv_ta is required. Enable it in lv_conf.h (LV_USE_TEXTAREA 1) " +#endif + +/********************* + * DEFINES + *********************/ +#define LV_KEYBOARD_CTRL_BTN_FLAGS (LV_BTNMATRIX_CTRL_NO_REPEAT | LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_CHECKED) + +/********************** + * TYPEDEFS + **********************/ + +/** Current keyboard mode.*/ +enum { + LV_KEYBOARD_MODE_TEXT_LOWER, + LV_KEYBOARD_MODE_TEXT_UPPER, + LV_KEYBOARD_MODE_SPECIAL, + LV_KEYBOARD_MODE_NUMBER, + LV_KEYBOARD_MODE_USER_1, + LV_KEYBOARD_MODE_USER_2, + LV_KEYBOARD_MODE_USER_3, + LV_KEYBOARD_MODE_USER_4, +}; +typedef uint8_t lv_keyboard_mode_t; + +/*Data of keyboard*/ +typedef struct { + lv_btnmatrix_t btnm; + lv_obj_t * ta; /*Pointer to the assigned text area*/ + lv_keyboard_mode_t mode; /*Key map type*/ + uint8_t popovers : 1; /*Show button titles in popovers on press*/ +} lv_keyboard_t; + +extern const lv_obj_class_t lv_keyboard_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a Keyboard object + * @param parent pointer to an object, it will be the parent of the new keyboard + * @return pointer to the created keyboard + */ +lv_obj_t * lv_keyboard_create(lv_obj_t * parent); + +/*===================== + * Setter functions + *====================*/ + +/** + * Assign a Text Area to the Keyboard. The pressed characters will be put there. + * @param kb pointer to a Keyboard object + * @param ta pointer to a Text Area object to write there + */ +void lv_keyboard_set_textarea(lv_obj_t * kb, lv_obj_t * ta); + +/** + * Set a new a mode (text or number map) + * @param kb pointer to a Keyboard object + * @param mode the mode from 'lv_keyboard_mode_t' + */ +void lv_keyboard_set_mode(lv_obj_t * kb, lv_keyboard_mode_t mode); + +/** + * Show the button title in a popover when pressed. + * @param kb pointer to a Keyboard object + * @param en whether "popovers" mode is enabled + */ +void lv_keyboard_set_popovers(lv_obj_t * kb, bool en); + +/** + * Set a new map for the keyboard + * @param kb pointer to a Keyboard object + * @param mode keyboard map to alter 'lv_keyboard_mode_t' + * @param map pointer to a string array to describe the map. + * See 'lv_btnmatrix_set_map()' for more info. + */ +void lv_keyboard_set_map(lv_obj_t * kb, lv_keyboard_mode_t mode, const char * map[], + const lv_btnmatrix_ctrl_t ctrl_map[]); + +/*===================== + * Getter functions + *====================*/ + +/** + * Assign a Text Area to the Keyboard. The pressed characters will be put there. + * @param kb pointer to a Keyboard object + * @return pointer to the assigned Text Area object + */ +lv_obj_t * lv_keyboard_get_textarea(const lv_obj_t * kb); + +/** + * Set a new a mode (text or number map) + * @param kb pointer to a Keyboard object + * @return the current mode from 'lv_keyboard_mode_t' + */ +lv_keyboard_mode_t lv_keyboard_get_mode(const lv_obj_t * kb); + +/** + * Tell whether "popovers" mode is enabled or not. + * @param kb pointer to a Keyboard object + * @return true: "popovers" mode is enabled; false: disabled + */ +bool lv_btnmatrix_get_popovers(const lv_obj_t * obj); + +/** + * Get the current map of a keyboard + * @param kb pointer to a keyboard object + * @return the current map + */ +static inline const char ** lv_keyboard_get_map_array(const lv_obj_t * kb) +{ + return lv_btnmatrix_get_map(kb); +} + +/** + * Get the index of the lastly "activated" button by the user (pressed, released, focused etc) + * Useful in the `event_cb` to get the text of the button, check if hidden etc. + * @param obj pointer to button matrix object + * @return index of the last released button (LV_BTNMATRIX_BTN_NONE: if unset) + */ +static inline uint16_t lv_keyboard_get_selected_btn(const lv_obj_t * obj) +{ + return lv_btnmatrix_get_selected_btn(obj); +} + +/** + * Get the button's text + * @param obj pointer to button matrix object + * @param btn_id the index a button not counting new line characters. + * @return text of btn_index` button + */ +static inline const char * lv_keyboard_get_btn_text(const lv_obj_t * obj, uint16_t btn_id) +{ + return lv_btnmatrix_get_btn_text(obj, btn_id); +} + +/*===================== + * Other functions + *====================*/ + +/** + * Default keyboard event to add characters to the Text area and change the map. + * If a custom `event_cb` is added to the keyboard this function can be called from it to handle the + * button clicks + * @param kb pointer to a keyboard + * @param event the triggering event + */ +void lv_keyboard_def_event_cb(lv_event_t * e); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_KEYBOARD*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_KEYBOARD_H*/ diff --git a/lib/lvgl/src/extra/widgets/led/lv_led.c b/lib/lvgl/src/extra/widgets/led/lv_led.c new file mode 100644 index 00000000..88b7b87d --- /dev/null +++ b/lib/lvgl/src/extra/widgets/led/lv_led.c @@ -0,0 +1,221 @@ +/** + * @file lv_led.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_led.h" +#if LV_USE_LED + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_led_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_led_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_led_event(const lv_obj_class_t * class_p, lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_led_class = { + .base_class = &lv_obj_class, + .constructor_cb = lv_led_constructor, + .width_def = LV_DPI_DEF / 5, + .height_def = LV_DPI_DEF / 5, + .event_cb = lv_led_event, + .instance_size = sizeof(lv_led_t), +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create a led object + * @param parent pointer to an object, it will be the parent of the new led + * @return pointer to the created led + */ +lv_obj_t * lv_led_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the color of the LED + * @param led pointer to a LED object + * @param color the color of the LED + */ +void lv_led_set_color(lv_obj_t * obj, lv_color_t color) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_led_t * led = (lv_led_t *)obj; + led->color = color; + lv_obj_invalidate(obj); +} + +/** + * Set the brightness of a LED object + * @param led pointer to a LED object + * @param bright LV_LED_BRIGHT_MIN (max. dark) ... LV_LED_BRIGHT_MAX (max. light) + */ +void lv_led_set_brightness(lv_obj_t * obj, uint8_t bright) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_led_t * led = (lv_led_t *)obj; + if(led->bright == bright) return; + + led->bright = LV_CLAMP(LV_LED_BRIGHT_MIN, bright, LV_LED_BRIGHT_MAX); + + /*Invalidate the object there fore it will be redrawn*/ + lv_obj_invalidate(obj); +} + +/** + * Light on a LED + * @param led pointer to a LED object + */ +void lv_led_on(lv_obj_t * led) +{ + lv_led_set_brightness(led, LV_LED_BRIGHT_MAX); +} + +/** + * Light off a LED + * @param led pointer to a LED object + */ +void lv_led_off(lv_obj_t * led) +{ + lv_led_set_brightness(led, LV_LED_BRIGHT_MIN); +} + +/** + * Toggle the state of a LED + * @param led pointer to a LED object + */ +void lv_led_toggle(lv_obj_t * obj) +{ + uint8_t bright = lv_led_get_brightness(obj); + if(bright > (LV_LED_BRIGHT_MIN + LV_LED_BRIGHT_MAX) >> 1) + lv_led_off(obj); + else + lv_led_on(obj); +} + +/*===================== + * Getter functions + *====================*/ + +/** + * Get the brightness of a LEd object + * @param led pointer to LED object + * @return bright 0 (max. dark) ... 255 (max. light) + */ +uint8_t lv_led_get_brightness(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_led_t * led = (lv_led_t *)obj; + return led->bright; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_led_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_led_t * led = (lv_led_t *)obj; + led->color = lv_theme_get_color_primary(obj); + led->bright = LV_LED_BRIGHT_MAX; +} + +static void lv_led_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + lv_res_t res; + + /* Call the ancestor's event handler */ + lv_event_code_t code = lv_event_get_code(e); + if(code != LV_EVENT_DRAW_MAIN && code != LV_EVENT_DRAW_MAIN_END) { + res = lv_obj_event_base(MY_CLASS, e); + if(res != LV_RES_OK) return; + } + + lv_obj_t * obj = lv_event_get_target(e); + if(code == LV_EVENT_DRAW_MAIN) { + /*Make darker colors in a temporary style according to the brightness*/ + lv_led_t * led = (lv_led_t *)obj; + + lv_draw_rect_dsc_t rect_dsc; + lv_draw_rect_dsc_init(&rect_dsc); + lv_obj_init_draw_rect_dsc(obj, LV_PART_MAIN, &rect_dsc); + + /*Use the original colors brightness to modify color->led*/ + rect_dsc.bg_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.bg_color)); + rect_dsc.bg_grad.stops[0].color = lv_color_mix(led->color, lv_color_black(), + lv_color_brightness(rect_dsc.bg_grad.stops[0].color)); + rect_dsc.bg_grad.stops[1].color = lv_color_mix(led->color, lv_color_black(), + lv_color_brightness(rect_dsc.bg_grad.stops[1].color)); + rect_dsc.shadow_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.shadow_color)); + rect_dsc.border_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.border_color)); + rect_dsc.outline_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.outline_color)); + + /*Mix. the color with black proportionally with brightness*/ + rect_dsc.bg_color = lv_color_mix(rect_dsc.bg_color, lv_color_black(), led->bright); + rect_dsc.bg_grad.stops[0].color = lv_color_mix(rect_dsc.bg_grad.stops[0].color, lv_color_black(), led->bright); + rect_dsc.bg_grad.stops[1].color = lv_color_mix(rect_dsc.bg_grad.stops[1].color, lv_color_black(), led->bright); + rect_dsc.border_color = lv_color_mix(rect_dsc.border_color, lv_color_black(), led->bright); + rect_dsc.shadow_color = lv_color_mix(rect_dsc.shadow_color, lv_color_black(), led->bright); + rect_dsc.outline_color = lv_color_mix(rect_dsc.outline_color, lv_color_black(), led->bright); + + /*Set the current shadow width according to brightness proportionally between LV_LED_BRIGHT_OFF + * and LV_LED_BRIGHT_ON*/ + rect_dsc.shadow_width = ((led->bright - LV_LED_BRIGHT_MIN) * rect_dsc.shadow_width) / + (LV_LED_BRIGHT_MAX - LV_LED_BRIGHT_MIN); + rect_dsc.shadow_spread = ((led->bright - LV_LED_BRIGHT_MIN) * rect_dsc.shadow_spread) / + (LV_LED_BRIGHT_MAX - LV_LED_BRIGHT_MIN); + + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.draw_area = &obj->coords; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_LED_DRAW_PART_RECTANGLE; + part_draw_dsc.rect_dsc = &rect_dsc; + part_draw_dsc.part = LV_PART_MAIN; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_rect(draw_ctx, &rect_dsc, &obj->coords); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } +} + +#endif diff --git a/lib/lvgl/src/extra/widgets/led/lv_led.h b/lib/lvgl/src/extra/widgets/led/lv_led.h new file mode 100644 index 00000000..368bcd23 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/led/lv_led.h @@ -0,0 +1,116 @@ +/** + * @file lv_led.h + * + */ + +#ifndef LV_LED_H +#define LV_LED_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_LED + + +/********************* + * DEFINES + *********************/ +/** Brightness when the LED if OFF */ +#ifndef LV_LED_BRIGHT_MIN +# define LV_LED_BRIGHT_MIN 80 +#endif + +/** Brightness when the LED if ON */ +#ifndef LV_LED_BRIGHT_MAX +# define LV_LED_BRIGHT_MAX 255 +#endif + +/********************** + * TYPEDEFS + **********************/ + +/*Data of led*/ +typedef struct { + lv_obj_t obj; + lv_color_t color; + uint8_t bright; /**< Current brightness of the LED (0..255)*/ +} lv_led_t; + +extern const lv_obj_class_t lv_led_class; + +/** + * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_led_class` + * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END` + */ +typedef enum { + LV_LED_DRAW_PART_RECTANGLE, /**< The main rectangle*/ +} lv_led_draw_part_type_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a led object + * @param parent pointer to an object, it will be the parent of the new led + * @return pointer to the created led + */ +lv_obj_t * lv_led_create(lv_obj_t * parent); + +/** + * Set the color of the LED + * @param led pointer to a LED object + * @param color the color of the LED + */ +void lv_led_set_color(lv_obj_t * led, lv_color_t color); + +/** + * Set the brightness of a LED object + * @param led pointer to a LED object + * @param bright LV_LED_BRIGHT_MIN (max. dark) ... LV_LED_BRIGHT_MAX (max. light) + */ +void lv_led_set_brightness(lv_obj_t * led, uint8_t bright); + +/** + * Light on a LED + * @param led pointer to a LED object + */ +void lv_led_on(lv_obj_t * led); + +/** + * Light off a LED + * @param led pointer to a LED object + */ +void lv_led_off(lv_obj_t * led); + +/** + * Toggle the state of a LED + * @param led pointer to a LED object + */ +void lv_led_toggle(lv_obj_t * led); + +/** + * Get the brightness of a LEd object + * @param led pointer to LED object + * @return bright 0 (max. dark) ... 255 (max. light) + */ +uint8_t lv_led_get_brightness(const lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_LED*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + + +#endif /*LV_LED_H*/ diff --git a/lib/lvgl/src/extra/widgets/list/lv_list.c b/lib/lvgl/src/extra/widgets/list/lv_list.c new file mode 100644 index 00000000..29355fd3 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/list/lv_list.c @@ -0,0 +1,120 @@ +/** + * @file lv_list.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_list.h" +#include "../../../core/lv_disp.h" +#include "../../../widgets/lv_label.h" +#include "../../../widgets/lv_img.h" +#include "../../../widgets/lv_btn.h" + +#if LV_USE_LIST + +/********************* + * DEFINES + *********************/ +#define MV_CLASS &lv_list + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +const lv_obj_class_t lv_list_class = { + .base_class = &lv_obj_class, + .width_def = (LV_DPI_DEF * 3) / 2, + .height_def = LV_DPI_DEF * 2 +}; + +const lv_obj_class_t lv_list_btn_class = { + .base_class = &lv_btn_class, +}; + +const lv_obj_class_t lv_list_text_class = { + .base_class = &lv_label_class, +}; + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_list_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_list_class, parent); + lv_obj_class_init_obj(obj); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + return obj; +} + +lv_obj_t * lv_list_add_text(lv_obj_t * list, const char * txt) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_list_text_class, list); + lv_obj_class_init_obj(obj); + lv_label_set_text(obj, txt); + lv_label_set_long_mode(obj, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_width(obj, LV_PCT(100)); + return obj; +} + +lv_obj_t * lv_list_add_btn(lv_obj_t * list, const void * icon, const char * txt) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_list_btn_class, list); + lv_obj_class_init_obj(obj); + lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + +#if LV_USE_IMG == 1 + if(icon) { + lv_obj_t * img = lv_img_create(obj); + lv_img_set_src(img, icon); + } +#endif + + if(txt) { + lv_obj_t * label = lv_label_create(obj); + lv_label_set_text(label, txt); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_flex_grow(label, 1); + } + + return obj; +} + +const char * lv_list_get_btn_text(lv_obj_t * list, lv_obj_t * btn) +{ + LV_UNUSED(list); + uint32_t i; + for(i = 0; i < lv_obj_get_child_cnt(btn); i++) { + lv_obj_t * child = lv_obj_get_child(btn, i); + if(lv_obj_check_type(child, &lv_label_class)) { + return lv_label_get_text(child); + } + + } + + return ""; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif /*LV_USE_LIST*/ diff --git a/lib/lvgl/src/extra/widgets/list/lv_list.h b/lib/lvgl/src/extra/widgets/list/lv_list.h new file mode 100644 index 00000000..0da5595b --- /dev/null +++ b/lib/lvgl/src/extra/widgets/list/lv_list.h @@ -0,0 +1,54 @@ +/** + * @file lv_win.h + * + */ + +#ifndef LV_LIST_H +#define LV_LIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" +#include "../../layouts/flex/lv_flex.h" + +#if LV_USE_LIST + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +extern const lv_obj_class_t lv_list_class; +extern const lv_obj_class_t lv_list_text_class; +extern const lv_obj_class_t lv_list_btn_class; +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_list_create(lv_obj_t * parent); + +lv_obj_t * lv_list_add_text(lv_obj_t * list, const char * txt); + +lv_obj_t * lv_list_add_btn(lv_obj_t * list, const void * icon, const char * txt); + +const char * lv_list_get_btn_text(lv_obj_t * list, lv_obj_t * btn); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_LIST*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_LIST_H*/ diff --git a/lib/lvgl/src/extra/widgets/lv_widgets.h b/lib/lvgl/src/extra/widgets/lv_widgets.h new file mode 100644 index 00000000..11418102 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/lv_widgets.h @@ -0,0 +1,56 @@ +/** + * @file lv_widgets.h + * + */ + +#ifndef LV_WIDGETS_H +#define LV_WIDGETS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "animimg/lv_animimg.h" +#include "calendar/lv_calendar.h" +#include "calendar/lv_calendar_header_arrow.h" +#include "calendar/lv_calendar_header_dropdown.h" +#include "chart/lv_chart.h" +#include "keyboard/lv_keyboard.h" +#include "list/lv_list.h" +#include "menu/lv_menu.h" +#include "msgbox/lv_msgbox.h" +#include "meter/lv_meter.h" +#include "spinbox/lv_spinbox.h" +#include "spinner/lv_spinner.h" +#include "tabview/lv_tabview.h" +#include "tileview/lv_tileview.h" +#include "win/lv_win.h" +#include "colorwheel/lv_colorwheel.h" +#include "led/lv_led.h" +#include "imgbtn/lv_imgbtn.h" +#include "span/lv_span.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_WIDGETS_H*/ diff --git a/lib/lvgl/src/extra/widgets/menu/lv_menu.c b/lib/lvgl/src/extra/widgets/menu/lv_menu.c new file mode 100644 index 00000000..78577e77 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/menu/lv_menu.c @@ -0,0 +1,767 @@ +/** + * @file lv_menu.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_menu.h" + +#if LV_USE_MENU + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_menu_class + +#include "../../../core/lv_obj.h" +#include "../../layouts/flex/lv_flex.h" +#include "../../../widgets/lv_label.h" +#include "../../../widgets/lv_btn.h" +#include "../../../widgets/lv_img.h" + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_menu_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_page_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_page_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_cont_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_section_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +const lv_obj_class_t lv_menu_class = { + .constructor_cb = lv_menu_constructor, + .destructor_cb = lv_menu_destructor, + .base_class = &lv_obj_class, + .width_def = (LV_DPI_DEF * 3) / 2, + .height_def = LV_DPI_DEF * 2, + .instance_size = sizeof(lv_menu_t) +}; +const lv_obj_class_t lv_menu_page_class = { + .constructor_cb = lv_menu_page_constructor, + .destructor_cb = lv_menu_page_destructor, + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT, + .instance_size = sizeof(lv_menu_page_t) +}; + +const lv_obj_class_t lv_menu_cont_class = { + .constructor_cb = lv_menu_cont_constructor, + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT +}; + +const lv_obj_class_t lv_menu_section_class = { + .constructor_cb = lv_menu_section_constructor, + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT +}; + +const lv_obj_class_t lv_menu_separator_class = { + .base_class = &lv_obj_class, + .width_def = LV_SIZE_CONTENT, + .height_def = LV_SIZE_CONTENT +}; + +const lv_obj_class_t lv_menu_sidebar_cont_class = { + .base_class = &lv_obj_class +}; + +const lv_obj_class_t lv_menu_main_cont_class = { + .base_class = &lv_obj_class +}; + +const lv_obj_class_t lv_menu_main_header_cont_class = { + .base_class = &lv_obj_class +}; + +const lv_obj_class_t lv_menu_sidebar_header_cont_class = { + .base_class = &lv_obj_class +}; + +static void lv_menu_refr(lv_obj_t * obj); +static void lv_menu_refr_sidebar_header_mode(lv_obj_t * obj); +static void lv_menu_refr_main_header_mode(lv_obj_t * obj); +static void lv_menu_load_page_event_cb(lv_event_t * e); +static void lv_menu_obj_del_event_cb(lv_event_t * e); +static void lv_menu_back_event_cb(lv_event_t * e); +static void lv_menu_value_changed_event_cb(lv_event_t * e); +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +bool lv_menu_item_back_btn_is_root(lv_obj_t * menu, lv_obj_t * obj); +void lv_menu_clear_history(lv_obj_t * obj); + +lv_obj_t * lv_menu_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_menu_page_create(lv_obj_t * parent, char * title) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_page_class, parent); + lv_obj_class_init_obj(obj); + + lv_menu_page_t * page = (lv_menu_page_t *)obj; + if(title) { + page->title = lv_mem_alloc(strlen(title) + 1); + LV_ASSERT_MALLOC(page->title); + if(page->title == NULL) return NULL; + strcpy(page->title, title); + } + else { + page->title = NULL; + } + + return obj; +} + +lv_obj_t * lv_menu_cont_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_cont_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_menu_section_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_section_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_menu_separator_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_separator_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +void lv_menu_refr(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + lv_ll_t * history_ll = &(menu->history_ll); + + /* The current menu */ + lv_menu_history_t * act_hist = _lv_ll_get_head(history_ll); + + lv_obj_t * page = NULL; + + if(act_hist != NULL) { + page = act_hist->page; + /* Delete the current item from the history */ + _lv_ll_remove(history_ll, act_hist); + lv_mem_free(act_hist); + menu->cur_depth--; + } + + /* Set it */ + lv_menu_set_page(obj, page); +} + +/*===================== + * Setter functions + *====================*/ + +void lv_menu_set_page(lv_obj_t * obj, lv_obj_t * page) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + /* Hide previous page */ + if(menu->main_page != NULL) { + lv_obj_set_parent(menu->main_page, menu->storage); + } + + if(page != NULL) { + /* Add a new node */ + lv_ll_t * history_ll = &(menu->history_ll); + lv_menu_history_t * new_node = _lv_ll_ins_head(history_ll); + LV_ASSERT_MALLOC(new_node); + new_node->page = page; + menu->cur_depth++; + + /* Place page in main */ + lv_obj_set_parent(page, menu->main); + } + else { + /* Empty page, clear history */ + lv_menu_clear_history(obj); + } + + menu->main_page = page; + + /* If there is a selected tab, update checked state */ + if(menu->selected_tab != NULL) { + if(menu->sidebar_page != NULL) { + lv_obj_add_state(menu->selected_tab, LV_STATE_CHECKED); + } + else { + lv_obj_clear_state(menu->selected_tab, LV_STATE_CHECKED); + } + } + + /* Back btn management */ + if(menu->sidebar_page != NULL) { + /* With sidebar enabled */ + if(menu->sidebar_generated) { + if(menu->mode_root_back_btn == LV_MENU_ROOT_BACK_BTN_ENABLED) { + /* Root back btn is always shown if enabled*/ + lv_obj_clear_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + else { + lv_obj_add_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + } + + if(menu->cur_depth >= 2) { + lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + else { + lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + } + else { + /* With sidebar disabled */ + if(menu->cur_depth >= 2 || menu->mode_root_back_btn == LV_MENU_ROOT_BACK_BTN_ENABLED) { + lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + else { + lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + } + + lv_event_send((lv_obj_t *)menu, LV_EVENT_VALUE_CHANGED, NULL); + + lv_menu_refr_main_header_mode(obj); +} + +void lv_menu_set_sidebar_page(lv_obj_t * obj, lv_obj_t * page) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + /* Sidebar management*/ + if(page != NULL) { + /* Sidebar should be enabled */ + if(!menu->sidebar_generated) { + /* Create sidebar */ + lv_obj_t * sidebar_cont = lv_obj_class_create_obj(&lv_menu_sidebar_cont_class, obj); + lv_obj_class_init_obj(sidebar_cont); + lv_obj_move_to_index(sidebar_cont, 1); + lv_obj_set_size(sidebar_cont, LV_PCT(30), LV_PCT(100)); + lv_obj_set_flex_flow(sidebar_cont, LV_FLEX_FLOW_COLUMN); + lv_obj_add_flag(sidebar_cont, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_clear_flag(sidebar_cont, LV_OBJ_FLAG_CLICKABLE); + menu->sidebar = sidebar_cont; + + lv_obj_t * sidebar_header = lv_obj_class_create_obj(&lv_menu_sidebar_header_cont_class, sidebar_cont); + lv_obj_class_init_obj(sidebar_header); + lv_obj_set_size(sidebar_header, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(sidebar_header, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(sidebar_header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_clear_flag(sidebar_header, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_flag(sidebar_header, LV_OBJ_FLAG_EVENT_BUBBLE); + menu->sidebar_header = sidebar_header; + + lv_obj_t * sidebar_header_back_btn = lv_btn_create(menu->sidebar_header); + lv_obj_add_event_cb(sidebar_header_back_btn, lv_menu_back_event_cb, LV_EVENT_CLICKED, menu); + lv_obj_add_flag(sidebar_header_back_btn, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_set_flex_flow(sidebar_header_back_btn, LV_FLEX_FLOW_ROW); + menu->sidebar_header_back_btn = sidebar_header_back_btn; + + lv_obj_t * sidebar_header_back_icon = lv_img_create(menu->sidebar_header_back_btn); + lv_img_set_src(sidebar_header_back_icon, LV_SYMBOL_LEFT); + + lv_obj_t * sidebar_header_title = lv_label_create(menu->sidebar_header); + lv_obj_add_flag(sidebar_header_title, LV_OBJ_FLAG_HIDDEN); + menu->sidebar_header_title = sidebar_header_title; + + menu->sidebar_generated = true; + } + + lv_obj_set_parent(page, menu->sidebar); + + lv_menu_refr_sidebar_header_mode(obj); + } + else { + /* Sidebar should be disabled */ + if(menu->sidebar_generated) { + lv_obj_set_parent(menu->sidebar_page, menu->storage); + lv_obj_del(menu->sidebar); + + menu->sidebar_generated = false; + } + } + + menu->sidebar_page = page; + lv_menu_refr(obj); +} + +void lv_menu_set_mode_header(lv_obj_t * obj, lv_menu_mode_header_t mode_header) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + if(menu->mode_header != mode_header) { + menu->mode_header = mode_header; + lv_menu_refr_main_header_mode(obj); + if(menu->sidebar_generated) lv_menu_refr_sidebar_header_mode(obj); + } +} + +void lv_menu_set_mode_root_back_btn(lv_obj_t * obj, lv_menu_mode_root_back_btn_t mode_root_back_btn) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + if(menu->mode_root_back_btn != mode_root_back_btn) { + menu->mode_root_back_btn = mode_root_back_btn; + lv_menu_refr(obj); + } +} + +void lv_menu_set_load_page_event(lv_obj_t * menu, lv_obj_t * obj, lv_obj_t * page) +{ + LV_ASSERT_OBJ(menu, MY_CLASS); + + lv_obj_add_flag(obj, LV_OBJ_FLAG_CLICKABLE); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS); + + /* Remove old event */ + if(lv_obj_remove_event_cb(obj, lv_menu_load_page_event_cb)) { + lv_event_send(obj, LV_EVENT_DELETE, NULL); + lv_obj_remove_event_cb(obj, lv_menu_obj_del_event_cb); + } + + lv_menu_load_page_event_data_t * event_data = lv_mem_alloc(sizeof(lv_menu_load_page_event_data_t)); + event_data->menu = menu; + event_data->page = page; + + lv_obj_add_event_cb(obj, lv_menu_load_page_event_cb, LV_EVENT_CLICKED, event_data); + lv_obj_add_event_cb(obj, lv_menu_obj_del_event_cb, LV_EVENT_DELETE, event_data); +} + +/*===================== + * Getter functions + *====================*/ +lv_obj_t * lv_menu_get_cur_main_page(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->main_page; +} + +lv_obj_t * lv_menu_get_cur_sidebar_page(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->sidebar_page; +} + +lv_obj_t * lv_menu_get_main_header(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->main_header; +} + +lv_obj_t * lv_menu_get_main_header_back_btn(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->main_header_back_btn; +} + +lv_obj_t * lv_menu_get_sidebar_header(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->sidebar_header; +} + +lv_obj_t * lv_menu_get_sidebar_header_back_btn(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->sidebar_header_back_btn; +} + +bool lv_menu_back_btn_is_root(lv_obj_t * menu, lv_obj_t * obj) +{ + LV_ASSERT_OBJ(menu, MY_CLASS); + + if(obj == ((lv_menu_t *)menu)->sidebar_header_back_btn) { + return true; + } + + if(obj == ((lv_menu_t *)menu)->main_header_back_btn && ((lv_menu_t *)menu)->prev_depth <= 1) { + return true; + } + + return false; +} + +void lv_menu_clear_history(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + lv_ll_t * history_ll = &(menu->history_ll); + + _lv_ll_clear(history_ll); + + menu->cur_depth = 0; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_menu_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_obj_set_layout(obj, LV_LAYOUT_FLEX); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + + lv_menu_t * menu = (lv_menu_t *)obj; + + menu->mode_header = LV_MENU_HEADER_TOP_FIXED; + menu->mode_root_back_btn = LV_MENU_ROOT_BACK_BTN_DISABLED; + menu->cur_depth = 0; + menu->prev_depth = 0; + menu->sidebar_generated = false; + + _lv_ll_init(&(menu->history_ll), sizeof(lv_menu_history_t)); + + menu->storage = lv_obj_create(obj); + lv_obj_add_flag(menu->storage, LV_OBJ_FLAG_HIDDEN); + + menu->sidebar = NULL; + menu->sidebar_header = NULL; + menu->sidebar_header_back_btn = NULL; + menu->sidebar_header_title = NULL; + menu->sidebar_page = NULL; + + lv_obj_t * main_cont = lv_obj_class_create_obj(&lv_menu_main_cont_class, obj); + lv_obj_class_init_obj(main_cont); + lv_obj_set_height(main_cont, LV_PCT(100)); + lv_obj_set_flex_grow(main_cont, 1); + lv_obj_set_flex_flow(main_cont, LV_FLEX_FLOW_COLUMN); + lv_obj_add_flag(main_cont, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_clear_flag(main_cont, LV_OBJ_FLAG_CLICKABLE); + menu->main = main_cont; + + lv_obj_t * main_header = lv_obj_class_create_obj(&lv_menu_main_header_cont_class, main_cont); + lv_obj_class_init_obj(main_header); + lv_obj_set_size(main_header, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(main_header, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(main_header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_clear_flag(main_header, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_flag(main_header, LV_OBJ_FLAG_EVENT_BUBBLE); + menu->main_header = main_header; + + /* Create the default simple back btn and title */ + lv_obj_t * main_header_back_btn = lv_btn_create(menu->main_header); + lv_obj_add_event_cb(main_header_back_btn, lv_menu_back_event_cb, LV_EVENT_CLICKED, menu); + lv_obj_add_flag(main_header_back_btn, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_set_flex_flow(main_header_back_btn, LV_FLEX_FLOW_ROW); + menu->main_header_back_btn = main_header_back_btn; + + lv_obj_t * main_header_back_icon = lv_img_create(menu->main_header_back_btn); + lv_img_set_src(main_header_back_icon, LV_SYMBOL_LEFT); + + lv_obj_t * main_header_title = lv_label_create(menu->main_header); + lv_obj_add_flag(main_header_title, LV_OBJ_FLAG_HIDDEN); + menu->main_header_title = main_header_title; + + menu->main_page = NULL; + menu->selected_tab = NULL; + + lv_obj_add_event_cb(obj, lv_menu_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, menu); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_menu_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_menu_t * menu = (lv_menu_t *)obj; + lv_ll_t * history_ll = &(menu->history_ll); + + _lv_ll_clear(history_ll); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_menu_page_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + + lv_menu_t * menu = (lv_menu_t *)lv_obj_get_parent(obj); + + lv_obj_set_parent(obj, ((lv_menu_t *)menu)->storage); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE); +} + +static void lv_menu_page_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + + lv_menu_page_t * page = (lv_menu_page_t *)obj; + + if(page->title != NULL) { + lv_mem_free(page->title); + page->title = NULL; + } +} + +static void lv_menu_cont_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + 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_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE); +} + +static void lv_menu_section_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE); +} + +static void lv_menu_refr_sidebar_header_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + if(menu->sidebar_header == NULL || menu->sidebar_page == NULL) return; + + switch(menu->mode_header) { + case LV_MENU_HEADER_TOP_FIXED: + /* Content should fill the remaining space */ + lv_obj_move_to_index(menu->sidebar_header, 0); + lv_obj_set_flex_grow(menu->sidebar_page, 1); + break; + case LV_MENU_HEADER_TOP_UNFIXED: + lv_obj_move_to_index(menu->sidebar_header, 0); + lv_obj_set_flex_grow(menu->sidebar_page, 0); + break; + case LV_MENU_HEADER_BOTTOM_FIXED: + lv_obj_move_to_index(menu->sidebar_header, 1); + lv_obj_set_flex_grow(menu->sidebar_page, 1); + break; + } + + lv_obj_refr_size(menu->sidebar_header); + lv_obj_refr_size(menu->sidebar_page); + + if(lv_obj_get_content_height(menu->sidebar_header) == 0) { + lv_obj_add_flag(menu->sidebar_header, LV_OBJ_FLAG_HIDDEN); + } + else { + lv_obj_clear_flag(menu->sidebar_header, LV_OBJ_FLAG_HIDDEN); + } +} + +static void lv_menu_refr_main_header_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + if(menu->main_header == NULL || menu->main_page == NULL) return; + + switch(menu->mode_header) { + case LV_MENU_HEADER_TOP_FIXED: + /* Content should fill the remaining space */ + lv_obj_move_to_index(menu->main_header, 0); + lv_obj_set_flex_grow(menu->main_page, 1); + break; + case LV_MENU_HEADER_TOP_UNFIXED: + lv_obj_move_to_index(menu->main_header, 0); + lv_obj_set_flex_grow(menu->main_page, 0); + break; + case LV_MENU_HEADER_BOTTOM_FIXED: + lv_obj_move_to_index(menu->main_header, 1); + lv_obj_set_flex_grow(menu->main_page, 1); + break; + } + + lv_obj_refr_size(menu->main_header); + lv_obj_refr_size(menu->main_page); + lv_obj_update_layout(menu->main_header); + + if(lv_obj_get_content_height(menu->main_header) == 0) { + lv_obj_add_flag(menu->main_header, LV_OBJ_FLAG_HIDDEN); + } + else { + lv_obj_clear_flag(menu->main_header, LV_OBJ_FLAG_HIDDEN); + } +} + +static void lv_menu_load_page_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_menu_load_page_event_data_t * event_data = lv_event_get_user_data(e); + lv_menu_t * menu = (lv_menu_t *)(event_data->menu); + lv_obj_t * page = event_data->page; + + if(menu->sidebar_page != NULL) { + /* Check if clicked obj is in the sidebar */ + bool sidebar = false; + lv_obj_t * parent = obj; + + while(parent) { + if(parent == (lv_obj_t *)menu) break; + if(parent == menu->sidebar) { + sidebar = true; + break; + } + parent = lv_obj_get_parent(parent); + } + + if(sidebar) { + /* Clear checked state of previous obj */ + if(menu->selected_tab != obj && menu->selected_tab != NULL) { + lv_obj_clear_state(menu->selected_tab, LV_STATE_CHECKED); + } + + lv_menu_clear_history((lv_obj_t *)menu); + + menu->selected_tab = obj; + } + } + + lv_menu_set_page((lv_obj_t *)menu, page); + + if(lv_group_get_default() != NULL && menu->sidebar_page == NULL) { + /* Sidebar is not supported for now*/ + lv_group_focus_next(lv_group_get_default()); + } +} + +static void lv_menu_obj_del_event_cb(lv_event_t * e) +{ + lv_menu_load_page_event_data_t * event_data = lv_event_get_user_data(e); + lv_mem_free(event_data); +} + +static void lv_menu_back_event_cb(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + /* LV_EVENT_CLICKED */ + if(code == LV_EVENT_CLICKED) { + lv_obj_t * obj = lv_event_get_target(e); + lv_menu_t * menu = (lv_menu_t *)lv_event_get_user_data(e); + + if(!(obj == menu->main_header_back_btn || obj == menu->sidebar_header_back_btn)) return; + + menu->prev_depth = menu->cur_depth; /* Save the previous value for user event handler */ + + if(lv_menu_back_btn_is_root((lv_obj_t *)menu, obj)) return; + + lv_ll_t * history_ll = &(menu->history_ll); + + /* The current menu */ + lv_menu_history_t * act_hist = _lv_ll_get_head(history_ll); + + /* The previous menu */ + lv_menu_history_t * prev_hist = _lv_ll_get_next(history_ll, act_hist); + + if(prev_hist != NULL) { + /* Previous menu exists */ + /* Delete the current item from the history */ + _lv_ll_remove(history_ll, act_hist); + lv_mem_free(act_hist); + menu->cur_depth--; + /* Create the previous menu. + * Remove it from the history because `lv_menu_set_page` will add it again */ + _lv_ll_remove(history_ll, prev_hist); + menu->cur_depth--; + lv_menu_set_page(&(menu->obj), prev_hist->page); + + lv_mem_free(prev_hist); + } + } +} + +static void lv_menu_value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_user_data(e); + lv_menu_t * menu = (lv_menu_t *)obj; + + lv_menu_page_t * main_page = (lv_menu_page_t *)lv_menu_get_cur_main_page(obj); + if(main_page != NULL && menu->main_header_title != NULL) { + if(main_page->title != NULL) { + lv_label_set_text(menu->main_header_title, main_page->title); + lv_obj_clear_flag(menu->main_header_title, LV_OBJ_FLAG_HIDDEN); + } + else { + lv_obj_add_flag(menu->main_header_title, LV_OBJ_FLAG_HIDDEN); + } + } + + lv_menu_page_t * sidebar_page = (lv_menu_page_t *)lv_menu_get_cur_sidebar_page(obj); + if(sidebar_page != NULL && menu->sidebar_header_title != NULL) { + if(sidebar_page->title != NULL) { + lv_label_set_text(menu->sidebar_header_title, sidebar_page->title); + lv_obj_clear_flag(menu->sidebar_header_title, LV_OBJ_FLAG_HIDDEN); + } + else { + lv_obj_add_flag(menu->sidebar_header_title, LV_OBJ_FLAG_HIDDEN); + } + } +} +#endif /*LV_USE_MENU*/ diff --git a/lib/lvgl/src/extra/widgets/menu/lv_menu.h b/lib/lvgl/src/extra/widgets/menu/lv_menu.h new file mode 100644 index 00000000..0449059f --- /dev/null +++ b/lib/lvgl/src/extra/widgets/menu/lv_menu.h @@ -0,0 +1,233 @@ +/** + * @file lv_menu.h + * + */ + +#ifndef LV_MENU_H +#define LV_MENU_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" + +#if LV_USE_MENU + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +enum { + LV_MENU_HEADER_TOP_FIXED, /* Header is positioned at the top */ + LV_MENU_HEADER_TOP_UNFIXED, /* Header is positioned at the top and can be scrolled out of view*/ + LV_MENU_HEADER_BOTTOM_FIXED /* Header is positioned at the bottom */ +}; +typedef uint8_t lv_menu_mode_header_t; + +enum { + LV_MENU_ROOT_BACK_BTN_DISABLED, + LV_MENU_ROOT_BACK_BTN_ENABLED +}; +typedef uint8_t lv_menu_mode_root_back_btn_t; + +typedef struct lv_menu_load_page_event_data_t { + lv_obj_t * menu; + lv_obj_t * page; +} lv_menu_load_page_event_data_t; + +typedef struct { + lv_obj_t * page; +} lv_menu_history_t; + +typedef struct { + lv_obj_t obj; + lv_obj_t * storage; /* a pointer to obj that is the parent of all pages not displayed */ + lv_obj_t * main; + lv_obj_t * main_page; + lv_obj_t * main_header; + lv_obj_t * + main_header_back_btn; /* a pointer to obj that on click triggers back btn event handler, can be same as 'main_header' */ + lv_obj_t * main_header_title; + lv_obj_t * sidebar; + lv_obj_t * sidebar_page; + lv_obj_t * sidebar_header; + lv_obj_t * + sidebar_header_back_btn; /* a pointer to obj that on click triggers back btn event handler, can be same as 'sidebar_header' */ + lv_obj_t * sidebar_header_title; + lv_obj_t * selected_tab; + lv_ll_t history_ll; + uint8_t cur_depth; + uint8_t prev_depth; + uint8_t sidebar_generated : 1; + lv_menu_mode_header_t mode_header : 2; + lv_menu_mode_root_back_btn_t mode_root_back_btn : 1; +} lv_menu_t; + +typedef struct { + lv_obj_t obj; + char * title; +} lv_menu_page_t; + +extern const lv_obj_class_t lv_menu_class; +extern const lv_obj_class_t lv_menu_page_class; +extern const lv_obj_class_t lv_menu_cont_class; +extern const lv_obj_class_t lv_menu_section_class; +extern const lv_obj_class_t lv_menu_separator_class; +extern const lv_obj_class_t lv_menu_sidebar_cont_class; +extern const lv_obj_class_t lv_menu_main_cont_class; +extern const lv_obj_class_t lv_menu_sidebar_header_cont_class; +extern const lv_obj_class_t lv_menu_main_header_cont_class; +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a menu object + * @param parent pointer to an object, it will be the parent of the new menu + * @return pointer to the created menu + */ +lv_obj_t * lv_menu_create(lv_obj_t * parent); + +/** + * Create a menu page object + * @param parent pointer to menu object + * @param title pointer to text for title in header (NULL to not display title) + * @return pointer to the created menu page + */ +lv_obj_t * lv_menu_page_create(lv_obj_t * parent, char * title); + +/** + * Create a menu cont object + * @param parent pointer to an object, it will be the parent of the new menu cont object + * @return pointer to the created menu cont + */ +lv_obj_t * lv_menu_cont_create(lv_obj_t * parent); + +/** + * Create a menu section object + * @param parent pointer to an object, it will be the parent of the new menu section object + * @return pointer to the created menu section + */ +lv_obj_t * lv_menu_section_create(lv_obj_t * parent); + +/** + * Create a menu separator object + * @param parent pointer to an object, it will be the parent of the new menu separator object + * @return pointer to the created menu separator + */ +lv_obj_t * lv_menu_separator_create(lv_obj_t * parent); +/*===================== + * Setter functions + *====================*/ +/** + * Set menu page to display in main + * @param obj pointer to the menu + * @param page pointer to the menu page to set (NULL to clear main and clear menu history) + */ +void lv_menu_set_page(lv_obj_t * obj, lv_obj_t * page); + +/** + * Set menu page to display in sidebar + * @param obj pointer to the menu + * @param page pointer to the menu page to set (NULL to clear sidebar) + */ +void lv_menu_set_sidebar_page(lv_obj_t * obj, lv_obj_t * page); + +/** + * Set the how the header should behave and its position + * @param obj pointer to a menu + * @param mode_header + */ +void lv_menu_set_mode_header(lv_obj_t * obj, lv_menu_mode_header_t mode_header); + +/** + * Set whether back button should appear at root + * @param obj pointer to a menu + * @param mode_root_back_btn + */ +void lv_menu_set_mode_root_back_btn(lv_obj_t * obj, lv_menu_mode_root_back_btn_t mode_root_back_btn); + +/** + * Add menu to the menu item + * @param menu pointer to the menu + * @param obj pointer to the obj + * @param page pointer to the page to load when obj is clicked + */ +void lv_menu_set_load_page_event(lv_obj_t * menu, lv_obj_t * obj, lv_obj_t * page); + +/*===================== + * Getter functions + *====================*/ +/** +* Get a pointer to menu page that is currently displayed in main +* @param obj pointer to the menu +* @return pointer to current page +*/ +lv_obj_t * lv_menu_get_cur_main_page(lv_obj_t * obj); + +/** +* Get a pointer to menu page that is currently displayed in sidebar +* @param obj pointer to the menu +* @return pointer to current page +*/ +lv_obj_t * lv_menu_get_cur_sidebar_page(lv_obj_t * obj); + +/** +* Get a pointer to main header obj +* @param obj pointer to the menu +* @return pointer to main header obj +*/ +lv_obj_t * lv_menu_get_main_header(lv_obj_t * obj); + +/** +* Get a pointer to main header back btn obj +* @param obj pointer to the menu +* @return pointer to main header back btn obj +*/ +lv_obj_t * lv_menu_get_main_header_back_btn(lv_obj_t * obj); + +/** +* Get a pointer to sidebar header obj +* @param obj pointer to the menu +* @return pointer to sidebar header obj +*/ +lv_obj_t * lv_menu_get_sidebar_header(lv_obj_t * obj); + +/** +* Get a pointer to sidebar header obj +* @param obj pointer to the menu +* @return pointer to sidebar header back btn obj +*/ +lv_obj_t * lv_menu_get_sidebar_header_back_btn(lv_obj_t * obj); + +/** + * Check if an obj is a root back btn + * @param menu pointer to the menu + * @return true if it is a root back btn + */ +bool lv_menu_back_btn_is_root(lv_obj_t * menu, lv_obj_t * obj); + +/** + * Clear menu history + * @param obj pointer to the menu + */ +void lv_menu_clear_history(lv_obj_t * obj); +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_MENU*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_MENU_H*/ diff --git a/lib/lvgl/src/extra/widgets/meter/lv_meter.c b/lib/lvgl/src/extra/widgets/meter/lv_meter.c new file mode 100644 index 00000000..668ab97e --- /dev/null +++ b/lib/lvgl/src/extra/widgets/meter/lv_meter.c @@ -0,0 +1,697 @@ +/** + * @file lv_meter.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_meter.h" +#if LV_USE_METER != 0 + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_meter_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_meter_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_meter_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_meter_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void draw_arcs(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area); +static void draw_ticks_and_labels(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area); +static void draw_needles(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area); +static void inv_arc(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t old_value, int32_t new_value); +static void inv_line(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_meter_class = { + .constructor_cb = lv_meter_constructor, + .destructor_cb = lv_meter_destructor, + .event_cb = lv_meter_event, + .instance_size = sizeof(lv_meter_t), + .base_class = &lv_obj_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_meter_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Add scale + *====================*/ + +lv_meter_scale_t * lv_meter_add_scale(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + + lv_meter_scale_t * scale = _lv_ll_ins_head(&meter->scale_ll); + LV_ASSERT_MALLOC(scale); + lv_memset_00(scale, sizeof(lv_meter_scale_t)); + + scale->angle_range = 270; + scale->rotation = 90 + (360 - scale->angle_range) / 2; + scale->min = 0; + scale->max = 100; + scale->tick_cnt = 6; + scale->tick_length = 8; + scale->tick_width = 2; + scale->label_gap = 2; + + return scale; +} + +void lv_meter_set_scale_ticks(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t cnt, uint16_t width, uint16_t len, + lv_color_t color) +{ + scale->tick_cnt = cnt; + scale->tick_width = width; + scale->tick_length = len; + scale->tick_color = color; + lv_obj_invalidate(obj); +} + +void lv_meter_set_scale_major_ticks(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t nth, uint16_t width, + uint16_t len, lv_color_t color, int16_t label_gap) +{ + scale->tick_major_nth = nth; + scale->tick_major_width = width; + scale->tick_major_length = len; + scale->tick_major_color = color; + scale->label_gap = label_gap; + lv_obj_invalidate(obj); +} + +void lv_meter_set_scale_range(lv_obj_t * obj, lv_meter_scale_t * scale, int32_t min, int32_t max, uint32_t angle_range, + uint32_t rotation) +{ + scale->min = min; + scale->max = max; + scale->angle_range = angle_range; + scale->rotation = rotation; + lv_obj_invalidate(obj); +} + +/*===================== + * Add indicator + *====================*/ + +lv_meter_indicator_t * lv_meter_add_needle_line(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t width, + lv_color_t color, int16_t r_mod) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + lv_meter_indicator_t * indic = _lv_ll_ins_head(&meter->indicator_ll); + LV_ASSERT_MALLOC(indic); + lv_memset_00(indic, sizeof(lv_meter_indicator_t)); + indic->scale = scale; + indic->opa = LV_OPA_COVER; + + indic->type = LV_METER_INDICATOR_TYPE_NEEDLE_LINE; + indic->type_data.needle_line.width = width; + indic->type_data.needle_line.color = color; + indic->type_data.needle_line.r_mod = r_mod; + lv_obj_invalidate(obj); + + return indic; +} + +lv_meter_indicator_t * lv_meter_add_needle_img(lv_obj_t * obj, lv_meter_scale_t * scale, const void * src, + lv_coord_t pivot_x, lv_coord_t pivot_y) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + lv_meter_indicator_t * indic = _lv_ll_ins_head(&meter->indicator_ll); + LV_ASSERT_MALLOC(indic); + lv_memset_00(indic, sizeof(lv_meter_indicator_t)); + indic->scale = scale; + indic->opa = LV_OPA_COVER; + + indic->type = LV_METER_INDICATOR_TYPE_NEEDLE_IMG; + indic->type_data.needle_img.src = src; + indic->type_data.needle_img.pivot.x = pivot_x; + indic->type_data.needle_img.pivot.y = pivot_y; + lv_obj_invalidate(obj); + + return indic; +} + +lv_meter_indicator_t * lv_meter_add_arc(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t width, lv_color_t color, + int16_t r_mod) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + lv_meter_indicator_t * indic = _lv_ll_ins_head(&meter->indicator_ll); + LV_ASSERT_MALLOC(indic); + lv_memset_00(indic, sizeof(lv_meter_indicator_t)); + indic->scale = scale; + indic->opa = LV_OPA_COVER; + + indic->type = LV_METER_INDICATOR_TYPE_ARC; + indic->type_data.arc.width = width; + indic->type_data.arc.color = color; + indic->type_data.arc.r_mod = r_mod; + + lv_obj_invalidate(obj); + return indic; +} + +lv_meter_indicator_t * lv_meter_add_scale_lines(lv_obj_t * obj, lv_meter_scale_t * scale, lv_color_t color_start, + lv_color_t color_end, bool local, int16_t width_mod) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + lv_meter_indicator_t * indic = _lv_ll_ins_head(&meter->indicator_ll); + LV_ASSERT_MALLOC(indic); + lv_memset_00(indic, sizeof(lv_meter_indicator_t)); + indic->scale = scale; + indic->opa = LV_OPA_COVER; + + indic->type = LV_METER_INDICATOR_TYPE_SCALE_LINES; + indic->type_data.scale_lines.color_start = color_start; + indic->type_data.scale_lines.color_end = color_end; + indic->type_data.scale_lines.local_grad = local; + indic->type_data.scale_lines.width_mod = width_mod; + + lv_obj_invalidate(obj); + return indic; +} + +/*===================== + * Set indicator value + *====================*/ + +void lv_meter_set_indicator_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value) +{ + int32_t old_start = indic->start_value; + int32_t old_end = indic->end_value; + indic->start_value = value; + indic->end_value = value; + + if(indic->type == LV_METER_INDICATOR_TYPE_ARC) { + inv_arc(obj, indic, old_start, value); + inv_arc(obj, indic, old_end, value); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG || indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + inv_line(obj, indic, old_start); + inv_line(obj, indic, old_end); + inv_line(obj, indic, value); + } + else { + lv_obj_invalidate(obj); + } +} + +void lv_meter_set_indicator_start_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value) +{ + int32_t old_value = indic->start_value; + indic->start_value = value; + + if(indic->type == LV_METER_INDICATOR_TYPE_ARC) { + inv_arc(obj, indic, old_value, value); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG || indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + inv_line(obj, indic, old_value); + inv_line(obj, indic, value); + } + else { + lv_obj_invalidate(obj); + } +} + +void lv_meter_set_indicator_end_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value) +{ + int32_t old_value = indic->end_value; + indic->end_value = value; + + if(indic->type == LV_METER_INDICATOR_TYPE_ARC) { + inv_arc(obj, indic, old_value, value); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG || indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + inv_line(obj, indic, old_value); + inv_line(obj, indic, value); + } + else { + lv_obj_invalidate(obj); + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_meter_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_meter_t * meter = (lv_meter_t *)obj; + + _lv_ll_init(&meter->scale_ll, sizeof(lv_meter_scale_t)); + _lv_ll_init(&meter->indicator_ll, sizeof(lv_meter_indicator_t)); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_meter_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + _lv_ll_clear(&meter->indicator_ll); + _lv_ll_clear(&meter->scale_ll); + +} + +static void lv_meter_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + lv_res_t res = lv_obj_event_base(MY_CLASS, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + if(code == LV_EVENT_DRAW_MAIN) { + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + lv_area_t scale_area; + lv_obj_get_content_coords(obj, &scale_area); + + draw_arcs(obj, draw_ctx, &scale_area); + draw_ticks_and_labels(obj, draw_ctx, &scale_area); + draw_needles(obj, draw_ctx, &scale_area); + + lv_coord_t r_edge = lv_area_get_width(&scale_area) / 2; + lv_point_t scale_center; + scale_center.x = scale_area.x1 + r_edge; + scale_center.y = scale_area.y1 + r_edge; + + lv_draw_rect_dsc_t mid_dsc; + lv_draw_rect_dsc_init(&mid_dsc); + lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &mid_dsc); + lv_coord_t w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2; + lv_coord_t h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2; + lv_area_t nm_cord; + nm_cord.x1 = scale_center.x - w; + nm_cord.y1 = scale_center.y - h; + nm_cord.x2 = scale_center.x + w; + nm_cord.y2 = scale_center.y + h; + lv_draw_rect(draw_ctx, &mid_dsc, &nm_cord); + } +} + +static void draw_arcs(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area) +{ + lv_meter_t * meter = (lv_meter_t *)obj; + + lv_draw_arc_dsc_t arc_dsc; + lv_draw_arc_dsc_init(&arc_dsc); + arc_dsc.rounded = lv_obj_get_style_arc_rounded(obj, LV_PART_ITEMS); + + lv_coord_t r_out = lv_area_get_width(scale_area) / 2 ; + lv_point_t scale_center; + scale_center.x = scale_area->x1 + r_out; + scale_center.y = scale_area->y1 + r_out; + + lv_opa_t opa_main = lv_obj_get_style_opa(obj, LV_PART_MAIN); + lv_meter_indicator_t * indic; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.arc_dsc = &arc_dsc; + part_draw_dsc.part = LV_PART_INDICATOR; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_METER_DRAW_PART_ARC; + + _LV_LL_READ_BACK(&meter->indicator_ll, indic) { + if(indic->type != LV_METER_INDICATOR_TYPE_ARC) continue; + + arc_dsc.color = indic->type_data.arc.color; + arc_dsc.width = indic->type_data.arc.width; + arc_dsc.opa = indic->opa > LV_OPA_MAX ? opa_main : (opa_main * indic->opa) >> 8; + + lv_meter_scale_t * scale = indic->scale; + + int32_t start_angle = lv_map(indic->start_value, scale->min, scale->max, scale->rotation, + scale->rotation + scale->angle_range); + int32_t end_angle = lv_map(indic->end_value, scale->min, scale->max, scale->rotation, + scale->rotation + scale->angle_range); + + part_draw_dsc.radius = r_out + indic->type_data.arc.r_mod; + part_draw_dsc.sub_part_ptr = indic; + part_draw_dsc.p1 = &scale_center; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_arc(draw_ctx, &arc_dsc, &scale_center, part_draw_dsc.radius, start_angle, end_angle); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } +} + +static void draw_ticks_and_labels(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area) +{ + lv_meter_t * meter = (lv_meter_t *)obj; + + lv_point_t p_center; + lv_coord_t r_edge = LV_MIN(lv_area_get_width(scale_area) / 2, lv_area_get_height(scale_area) / 2); + p_center.x = scale_area->x1 + r_edge; + p_center.y = scale_area->y1 + r_edge; + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc); + line_dsc.raw_end = 1; + + lv_draw_label_dsc_t label_dsc; + lv_draw_label_dsc_init(&label_dsc); + lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc); + + lv_meter_scale_t * scale; + + lv_draw_mask_radius_param_t inner_minor_mask; + lv_draw_mask_radius_param_t inner_major_mask; + lv_draw_mask_radius_param_t outer_mask; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.part = LV_PART_TICKS; + part_draw_dsc.type = LV_METER_DRAW_PART_TICK; + part_draw_dsc.line_dsc = &line_dsc; + + _LV_LL_READ_BACK(&meter->scale_ll, scale) { + part_draw_dsc.sub_part_ptr = scale; + + lv_coord_t r_out = r_edge; + lv_coord_t r_in_minor = r_out - scale->tick_length; + lv_coord_t r_in_major = r_out - scale->tick_major_length; + + lv_area_t area_inner_minor; + area_inner_minor.x1 = p_center.x - r_in_minor; + area_inner_minor.y1 = p_center.y - r_in_minor; + area_inner_minor.x2 = p_center.x + r_in_minor; + area_inner_minor.y2 = p_center.y + r_in_minor; + lv_draw_mask_radius_init(&inner_minor_mask, &area_inner_minor, LV_RADIUS_CIRCLE, true); + + lv_area_t area_inner_major; + area_inner_major.x1 = p_center.x - r_in_major; + area_inner_major.y1 = p_center.y - r_in_major; + area_inner_major.x2 = p_center.x + r_in_major - 1; + area_inner_major.y2 = p_center.y + r_in_major - 1; + lv_draw_mask_radius_init(&inner_major_mask, &area_inner_major, LV_RADIUS_CIRCLE, true); + + lv_area_t area_outer; + area_outer.x1 = p_center.x - r_out; + area_outer.y1 = p_center.y - r_out; + area_outer.x2 = p_center.x + r_out - 1; + area_outer.y2 = p_center.y + r_out - 1; + lv_draw_mask_radius_init(&outer_mask, &area_outer, LV_RADIUS_CIRCLE, false); + int16_t outer_mask_id = lv_draw_mask_add(&outer_mask, NULL); + + int16_t inner_act_mask_id = LV_MASK_ID_INV; /*Will be added later*/ + + uint32_t minor_cnt = scale->tick_major_nth ? scale->tick_major_nth - 1 : 0xFFFF; + uint16_t i; + for(i = 0; i < scale->tick_cnt; i++) { + minor_cnt++; + bool major = false; + if(minor_cnt == scale->tick_major_nth) { + minor_cnt = 0; + major = true; + } + + int32_t value_of_line = lv_map(i, 0, scale->tick_cnt - 1, scale->min, scale->max); + part_draw_dsc.value = value_of_line; + + lv_color_t line_color = major ? scale->tick_major_color : scale->tick_color; + lv_color_t line_color_ori = line_color; + + lv_coord_t line_width_ori = major ? scale->tick_major_width : scale->tick_width; + lv_coord_t line_width = line_width_ori; + + lv_meter_indicator_t * indic; + _LV_LL_READ_BACK(&meter->indicator_ll, indic) { + if(indic->type != LV_METER_INDICATOR_TYPE_SCALE_LINES) continue; + if(value_of_line >= indic->start_value && value_of_line <= indic->end_value) { + line_width += indic->type_data.scale_lines.width_mod; + + if(indic->type_data.scale_lines.color_start.full == indic->type_data.scale_lines.color_end.full) { + line_color = indic->type_data.scale_lines.color_start; + } + else { + lv_opa_t ratio; + if(indic->type_data.scale_lines.local_grad) { + ratio = lv_map(value_of_line, indic->start_value, indic->end_value, LV_OPA_TRANSP, LV_OPA_COVER); + } + else { + ratio = lv_map(value_of_line, scale->min, scale->max, LV_OPA_TRANSP, LV_OPA_COVER); + } + line_color = lv_color_mix(indic->type_data.scale_lines.color_end, indic->type_data.scale_lines.color_start, ratio); + } + } + } + + int32_t angle_upscale = ((i * scale->angle_range) * 10) / (scale->tick_cnt - 1) + + scale->rotation * 10; + + line_dsc.color = line_color; + line_dsc.width = line_width; + + /*Draw a little bit longer lines to be sure the mask will clip them correctly + *and to get a better precision*/ + lv_point_t p_outer; + p_outer.x = p_center.x + r_out + LV_MAX(LV_DPI_DEF, r_out); + p_outer.y = p_center.y; + lv_point_transform(&p_outer, angle_upscale, 256, &p_center); + + part_draw_dsc.p1 = &p_center; + part_draw_dsc.p2 = &p_outer; + part_draw_dsc.id = i; + part_draw_dsc.label_dsc = &label_dsc; + + /*Draw the text*/ + if(major) { + lv_draw_mask_remove_id(outer_mask_id); + uint32_t r_text = r_in_major - scale->label_gap; + lv_point_t p; + p.x = p_center.x + r_text; + p.y = p_center.y; + lv_point_transform(&p, angle_upscale, 256, &p_center); + + lv_draw_label_dsc_t label_dsc_tmp; + lv_memcpy(&label_dsc_tmp, &label_dsc, sizeof(label_dsc_tmp)); + + part_draw_dsc.label_dsc = &label_dsc_tmp; + char buf[16]; + + lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, value_of_line); + part_draw_dsc.text = buf; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + lv_point_t label_size; + lv_txt_get_size(&label_size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, + LV_COORD_MAX, LV_TEXT_FLAG_NONE); + + lv_area_t label_cord; + label_cord.x1 = p.x - label_size.x / 2; + label_cord.y1 = p.y - label_size.y / 2; + label_cord.x2 = label_cord.x1 + label_size.x; + label_cord.y2 = label_cord.y1 + label_size.y; + + lv_draw_label(draw_ctx, part_draw_dsc.label_dsc, &label_cord, part_draw_dsc.text, NULL); + + outer_mask_id = lv_draw_mask_add(&outer_mask, NULL); + } + else { + part_draw_dsc.label_dsc = NULL; + part_draw_dsc.text = NULL; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + } + + inner_act_mask_id = lv_draw_mask_add(major ? &inner_major_mask : &inner_minor_mask, NULL); + lv_draw_line(draw_ctx, &line_dsc, &p_outer, &p_center); + lv_draw_mask_remove_id(inner_act_mask_id); + lv_event_send(obj, LV_EVENT_DRAW_MAIN_END, &part_draw_dsc); + + line_dsc.color = line_color_ori; + line_dsc.width = line_width_ori; + + } + lv_draw_mask_free_param(&inner_minor_mask); + lv_draw_mask_free_param(&inner_major_mask); + lv_draw_mask_free_param(&outer_mask); + lv_draw_mask_remove_id(outer_mask_id); + } +} + + +static void draw_needles(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area) +{ + lv_meter_t * meter = (lv_meter_t *)obj; + + lv_coord_t r_edge = lv_area_get_width(scale_area) / 2; + lv_point_t scale_center; + scale_center.x = scale_area->x1 + r_edge; + scale_center.y = scale_area->y1 + r_edge; + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc); + + lv_draw_img_dsc_t img_dsc; + lv_draw_img_dsc_init(&img_dsc); + lv_obj_init_draw_img_dsc(obj, LV_PART_ITEMS, &img_dsc); + lv_opa_t opa_main = lv_obj_get_style_opa(obj, LV_PART_MAIN); + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.p1 = &scale_center; + + lv_meter_indicator_t * indic; + _LV_LL_READ_BACK(&meter->indicator_ll, indic) { + lv_meter_scale_t * scale = indic->scale; + part_draw_dsc.sub_part_ptr = indic; + + if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + int32_t angle = lv_map(indic->end_value, scale->min, scale->max, scale->rotation, scale->rotation + scale->angle_range); + lv_coord_t r_out = r_edge + scale->r_mod + indic->type_data.needle_line.r_mod; + lv_point_t p_end; + p_end.y = (lv_trigo_sin(angle) * (r_out)) / LV_TRIGO_SIN_MAX + scale_center.y; + p_end.x = (lv_trigo_cos(angle) * (r_out)) / LV_TRIGO_SIN_MAX + scale_center.x; + line_dsc.color = indic->type_data.needle_line.color; + line_dsc.width = indic->type_data.needle_line.width; + line_dsc.opa = indic->opa > LV_OPA_MAX ? opa_main : (opa_main * indic->opa) >> 8; + + part_draw_dsc.id = LV_METER_DRAW_PART_NEEDLE_LINE; + part_draw_dsc.line_dsc = &line_dsc; + part_draw_dsc.p2 = &p_end; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc, &scale_center, &p_end); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG) { + if(indic->type_data.needle_img.src == NULL) continue; + + int32_t angle = lv_map(indic->end_value, scale->min, scale->max, scale->rotation, scale->rotation + scale->angle_range); + lv_img_header_t info; + lv_img_decoder_get_info(indic->type_data.needle_img.src, &info); + lv_area_t a; + a.x1 = scale_center.x - indic->type_data.needle_img.pivot.x; + a.y1 = scale_center.y - indic->type_data.needle_img.pivot.y; + a.x2 = a.x1 + info.w - 1; + a.y2 = a.y1 + info.h - 1; + + img_dsc.opa = indic->opa > LV_OPA_MAX ? opa_main : (opa_main * indic->opa) >> 8; + img_dsc.pivot.x = indic->type_data.needle_img.pivot.x; + img_dsc.pivot.y = indic->type_data.needle_img.pivot.y; + angle = angle * 10; + if(angle > 3600) angle -= 3600; + img_dsc.angle = angle; + + part_draw_dsc.img_dsc = &img_dsc; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_img(draw_ctx, &img_dsc, &a, indic->type_data.needle_img.src); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } +} + +static void inv_arc(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t old_value, int32_t new_value) +{ + bool rounded = lv_obj_get_style_arc_rounded(obj, LV_PART_ITEMS); + + lv_area_t scale_area; + lv_obj_get_content_coords(obj, &scale_area); + + lv_coord_t r_out = lv_area_get_width(&scale_area) / 2; + lv_point_t scale_center; + scale_center.x = scale_area.x1 + r_out; + scale_center.y = scale_area.y1 + r_out; + + r_out += indic->type_data.arc.r_mod; + + lv_meter_scale_t * scale = indic->scale; + + int32_t start_angle = lv_map(old_value, scale->min, scale->max, scale->rotation, scale->angle_range + scale->rotation); + int32_t end_angle = lv_map(new_value, scale->min, scale->max, scale->rotation, scale->angle_range + scale->rotation); + + lv_area_t a; + lv_draw_arc_get_area(scale_center.x, scale_center.y, r_out, LV_MIN(start_angle, end_angle), LV_MAX(start_angle, + end_angle), indic->type_data.arc.width, rounded, &a); + lv_obj_invalidate_area(obj, &a); +} + + +static void inv_line(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value) +{ + lv_area_t scale_area; + lv_obj_get_content_coords(obj, &scale_area); + + lv_coord_t r_out = lv_area_get_width(&scale_area) / 2; + lv_point_t scale_center; + scale_center.x = scale_area.x1 + r_out; + scale_center.y = scale_area.y1 + r_out; + + lv_meter_scale_t * scale = indic->scale; + + if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + int32_t angle = lv_map(value, scale->min, scale->max, scale->rotation, scale->rotation + scale->angle_range); + r_out += scale->r_mod + indic->type_data.needle_line.r_mod; + lv_point_t p_end; + p_end.y = (lv_trigo_sin(angle) * (r_out)) / LV_TRIGO_SIN_MAX + scale_center.y; + p_end.x = (lv_trigo_cos(angle) * (r_out)) / LV_TRIGO_SIN_MAX + scale_center.x; + + lv_area_t a; + a.x1 = LV_MIN(scale_center.x, p_end.x) - indic->type_data.needle_line.width - 2; + a.y1 = LV_MIN(scale_center.y, p_end.y) - indic->type_data.needle_line.width - 2; + a.x2 = LV_MAX(scale_center.x, p_end.x) + indic->type_data.needle_line.width + 2; + a.y2 = LV_MAX(scale_center.y, p_end.y) + indic->type_data.needle_line.width + 2; + + lv_obj_invalidate_area(obj, &a); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG) { + int32_t angle = lv_map(value, scale->min, scale->max, scale->rotation, scale->rotation + scale->angle_range); + lv_img_header_t info; + lv_img_decoder_get_info(indic->type_data.needle_img.src, &info); + + angle = angle * 10; + if(angle > 3600) angle -= 3600; + + scale_center.x -= indic->type_data.needle_img.pivot.x; + scale_center.y -= indic->type_data.needle_img.pivot.y; + lv_area_t a; + _lv_img_buf_get_transformed_area(&a, info.w, info.h, angle, LV_IMG_ZOOM_NONE, &indic->type_data.needle_img.pivot); + a.x1 += scale_center.x - 2; + a.y1 += scale_center.y - 2; + a.x2 += scale_center.x + 2; + a.y2 += scale_center.y + 2; + + lv_obj_invalidate_area(obj, &a); + } +} +#endif diff --git a/lib/lvgl/src/extra/widgets/meter/lv_meter.h b/lib/lvgl/src/extra/widgets/meter/lv_meter.h new file mode 100644 index 00000000..24c1dae0 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/meter/lv_meter.h @@ -0,0 +1,267 @@ +/** + * @file lv_meter.h + * + */ + +#ifndef LV_METER_H +#define LV_METER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_METER != 0 + +/*Testing of dependencies*/ +#if LV_DRAW_COMPLEX == 0 +#error "lv_meter: Complex drawing is required. Enable it in lv_conf.h (LV_DRAW_COMPLEX 1)" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_color_t tick_color; + uint16_t tick_cnt; + uint16_t tick_length; + uint16_t tick_width; + + lv_color_t tick_major_color; + uint16_t tick_major_nth; + uint16_t tick_major_length; + uint16_t tick_major_width; + + int16_t label_gap; + int16_t label_color; + + int32_t min; + int32_t max; + int16_t r_mod; + uint16_t angle_range; + int16_t rotation; +} lv_meter_scale_t; + +enum { + LV_METER_INDICATOR_TYPE_NEEDLE_IMG, + LV_METER_INDICATOR_TYPE_NEEDLE_LINE, + LV_METER_INDICATOR_TYPE_SCALE_LINES, + LV_METER_INDICATOR_TYPE_ARC, +}; +typedef uint8_t lv_meter_indicator_type_t; + +typedef struct { + lv_meter_scale_t * scale; + lv_meter_indicator_type_t type; + lv_opa_t opa; + int32_t start_value; + int32_t end_value; + union { + struct { + const void * src; + lv_point_t pivot; + } needle_img; + struct { + uint16_t width; + int16_t r_mod; + lv_color_t color; + } needle_line; + struct { + uint16_t width; + const void * src; + lv_color_t color; + int16_t r_mod; + } arc; + struct { + int16_t width_mod; + lv_color_t color_start; + lv_color_t color_end; + uint8_t local_grad : 1; + } scale_lines; + } type_data; +} lv_meter_indicator_t; + +/*Data of line meter*/ +typedef struct { + lv_obj_t obj; + lv_ll_t scale_ll; + lv_ll_t indicator_ll; +} lv_meter_t; + +extern const lv_obj_class_t lv_meter_class; + +/** + * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_meter_class` + * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END` + */ +typedef enum { + LV_METER_DRAW_PART_ARC, /**< The arc indicator*/ + LV_METER_DRAW_PART_NEEDLE_LINE, /**< The needle lines*/ + LV_METER_DRAW_PART_NEEDLE_IMG, /**< The needle images*/ + LV_METER_DRAW_PART_TICK, /**< The tick lines and labels*/ +} lv_meter_draw_part_type_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a Meter object + * @param parent pointer to an object, it will be the parent of the new bar. + * @return pointer to the created meter + */ +lv_obj_t * lv_meter_create(lv_obj_t * parent); + +/*===================== + * Add scale + *====================*/ + +/** + * Add a new scale to the meter. + * @param obj pointer to a meter object + * @return the new scale + * @note Indicators can be attached to scales. + */ +lv_meter_scale_t * lv_meter_add_scale(lv_obj_t * obj); + +/** + * Set the properties of the ticks of a scale + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param cnt number of tick lines + * @param width width of tick lines + * @param len length of tick lines + * @param color color of tick lines + */ +void lv_meter_set_scale_ticks(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t cnt, uint16_t width, uint16_t len, + lv_color_t color); + +/** + * Make some "normal" ticks major ticks and set their attributes. + * Texts with the current value are also added to the major ticks. + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param nth make every Nth normal tick major tick. (start from the first on the left) + * @param width width of the major ticks + * @param len length of the major ticks + * @param color color of the major ticks + * @param label_gap gap between the major ticks and the labels + */ +void lv_meter_set_scale_major_ticks(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t nth, uint16_t width, + uint16_t len, lv_color_t color, int16_t label_gap); + +/** + * Set the value and angular range of a scale. + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param min the minimum value + * @param max the maximal value + * @param angle_range the angular range of the scale + * @param rotation the angular offset from the 3 o'clock position (clock-wise) + */ +void lv_meter_set_scale_range(lv_obj_t * obj, lv_meter_scale_t * scale, int32_t min, int32_t max, uint32_t angle_range, + uint32_t rotation); + +/*===================== + * Add indicator + *====================*/ + +/** + * Add a needle line indicator the scale + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param width width of the line + * @param color color of the line + * @param r_mod the radius modifier (added to the scale's radius) to get the lines length + * @return the new indicator + */ +lv_meter_indicator_t * lv_meter_add_needle_line(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t width, + lv_color_t color, int16_t r_mod); + +/** + * Add a needle image indicator the scale + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param src the image source of the indicator. path or pointer to ::lv_img_dsc_t + * @param pivot_x the X pivot point of the needle + * @param pivot_y the Y pivot point of the needle + * @return the new indicator + * @note the needle image should point to the right, like -O-----> + */ +lv_meter_indicator_t * lv_meter_add_needle_img(lv_obj_t * obj, lv_meter_scale_t * scale, const void * src, + lv_coord_t pivot_x, lv_coord_t pivot_y); + +/** + * Add an arc indicator the scale + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param width width of the arc + * @param color color of the arc + * @param r_mod the radius modifier (added to the scale's radius) to get the outer radius of the arc + * @return the new indicator + */ +lv_meter_indicator_t * lv_meter_add_arc(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t width, lv_color_t color, + int16_t r_mod); + + +/** + * Add a scale line indicator the scale. It will modify the ticks. + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param color_start the start color + * @param color_end the end color + * @param local tell how to map start and end color. true: the indicator's start and end_value; false: the scale's min max value + * @param width_mod add this the affected tick's width + * @return the new indicator + */ +lv_meter_indicator_t * lv_meter_add_scale_lines(lv_obj_t * obj, lv_meter_scale_t * scale, lv_color_t color_start, + lv_color_t color_end, bool local, int16_t width_mod); + +/*===================== + * Set indicator value + *====================*/ + +/** + * Set the value of the indicator. It will set start and and value to the same value + * @param obj pointer to a meter object + * @param indic pointer to an indicator + * @param value the new value + */ +void lv_meter_set_indicator_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value); + +/** + * Set the start value of the indicator. + * @param obj pointer to a meter object + * @param indic pointer to an indicator + * @param value the new value + */ +void lv_meter_set_indicator_start_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value); + +/** + * Set the start value of the indicator. + * @param obj pointer to a meter object + * @param indic pointer to an indicator + * @param value the new value + */ +void lv_meter_set_indicator_end_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_METER*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_METER_H*/ diff --git a/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.c b/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.c new file mode 100644 index 00000000..8db5df7e --- /dev/null +++ b/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.c @@ -0,0 +1,209 @@ +/** + * @file lv_msgbox.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_msgbox.h" +#if LV_USE_MSGBOX + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define LV_MSGBOX_FLAG_AUTO_PARENT LV_OBJ_FLAG_WIDGET_1 /*Mark that the parent was automatically created*/ +#define MY_CLASS &lv_msgbox_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void msgbox_close_click_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_msgbox_class = { + .base_class = &lv_obj_class, + .width_def = LV_DPI_DEF * 2, + .height_def = LV_SIZE_CONTENT, + .instance_size = sizeof(lv_msgbox_t) +}; + +const lv_obj_class_t lv_msgbox_content_class = { + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT, + .instance_size = sizeof(lv_obj_t) +}; + +const lv_obj_class_t lv_msgbox_backdrop_class = { + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_PCT(100), + .instance_size = sizeof(lv_obj_t) +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_msgbox_create(lv_obj_t * parent, const char * title, const char * txt, const char * btn_txts[], + bool add_close_btn) +{ + LV_LOG_INFO("begin"); + bool auto_parent = false; + if(parent == NULL) { + auto_parent = true; + parent = lv_obj_class_create_obj(&lv_msgbox_backdrop_class, lv_layer_top()); + LV_ASSERT_MALLOC(parent); + lv_obj_class_init_obj(parent); + lv_obj_clear_flag(parent, LV_OBJ_FLAG_IGNORE_LAYOUT); + lv_obj_set_size(parent, LV_PCT(100), LV_PCT(100)); + } + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_msgbox_class, parent); + LV_ASSERT_MALLOC(obj); + if(obj == NULL) return NULL; + lv_obj_class_init_obj(obj); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + + if(auto_parent) lv_obj_add_flag(obj, LV_MSGBOX_FLAG_AUTO_PARENT); + + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW_WRAP); + + bool has_title = title && strlen(title) > 0; + + /*When a close button is required, we need the empty label as spacer to push the button to the right*/ + if(add_close_btn || has_title) { + mbox->title = lv_label_create(obj); + lv_label_set_text(mbox->title, has_title ? title : ""); + lv_label_set_long_mode(mbox->title, LV_LABEL_LONG_SCROLL_CIRCULAR); + if(add_close_btn) lv_obj_set_flex_grow(mbox->title, 1); + else lv_obj_set_width(mbox->title, LV_PCT(100)); + } + + if(add_close_btn) { + mbox->close_btn = lv_btn_create(obj); + lv_obj_set_ext_click_area(mbox->close_btn, LV_DPX(10)); + lv_obj_add_event_cb(mbox->close_btn, msgbox_close_click_event_cb, LV_EVENT_CLICKED, NULL); + lv_obj_t * label = lv_label_create(mbox->close_btn); + lv_label_set_text(label, LV_SYMBOL_CLOSE); + const lv_font_t * font = lv_obj_get_style_text_font(mbox->close_btn, LV_PART_MAIN); + lv_coord_t close_btn_size = lv_font_get_line_height(font) + LV_DPX(10); + lv_obj_set_size(mbox->close_btn, close_btn_size, close_btn_size); + lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); + } + + mbox->content = lv_obj_class_create_obj(&lv_msgbox_content_class, obj); + + bool has_txt = txt && strlen(txt) > 0; + if(has_txt) { + mbox->text = lv_label_create(mbox->content); + lv_label_set_text(mbox->text, txt); + lv_label_set_long_mode(mbox->text, LV_LABEL_LONG_WRAP); + lv_obj_set_width(mbox->text, lv_pct(100)); + } + + if(btn_txts) { + mbox->btns = lv_btnmatrix_create(obj); + lv_btnmatrix_set_map(mbox->btns, btn_txts); + lv_btnmatrix_set_btn_ctrl_all(mbox->btns, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT); + + uint32_t btn_cnt = 0; + while(btn_txts[btn_cnt] && btn_txts[btn_cnt][0] != '\0') { + btn_cnt++; + } + + const lv_font_t * font = lv_obj_get_style_text_font(mbox->btns, LV_PART_ITEMS); + lv_coord_t btn_h = lv_font_get_line_height(font) + LV_DPI_DEF / 10; + lv_obj_set_size(mbox->btns, btn_cnt * (2 * LV_DPI_DEF / 3), btn_h); + lv_obj_set_style_max_width(mbox->btns, lv_pct(100), 0); + lv_obj_add_flag(mbox->btns, LV_OBJ_FLAG_EVENT_BUBBLE); /*To see the event directly on the message box*/ + } + + return obj; +} + + +lv_obj_t * lv_msgbox_get_title(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->title; +} + +lv_obj_t * lv_msgbox_get_close_btn(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->close_btn; +} + +lv_obj_t * lv_msgbox_get_text(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->text; +} + +lv_obj_t * lv_msgbox_get_content(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->content; +} + +lv_obj_t * lv_msgbox_get_btns(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->btns; +} + +uint16_t lv_msgbox_get_active_btn(lv_obj_t * mbox) +{ + lv_obj_t * btnm = lv_msgbox_get_btns(mbox); + return lv_btnmatrix_get_selected_btn(btnm); +} + +const char * lv_msgbox_get_active_btn_text(lv_obj_t * mbox) +{ + lv_obj_t * btnm = lv_msgbox_get_btns(mbox); + return lv_btnmatrix_get_btn_text(btnm, lv_btnmatrix_get_selected_btn(btnm)); +} + +void lv_msgbox_close(lv_obj_t * mbox) +{ + if(lv_obj_has_flag(mbox, LV_MSGBOX_FLAG_AUTO_PARENT)) lv_obj_del(lv_obj_get_parent(mbox)); + else lv_obj_del(mbox); +} + +void lv_msgbox_close_async(lv_obj_t * dialog) +{ + if(lv_obj_has_flag(dialog, LV_MSGBOX_FLAG_AUTO_PARENT)) lv_obj_del_async(lv_obj_get_parent(dialog)); + else lv_obj_del_async(dialog); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void msgbox_close_click_event_cb(lv_event_t * e) +{ + lv_obj_t * btn = lv_event_get_target(e); + lv_obj_t * mbox = lv_obj_get_parent(btn); + lv_msgbox_close(mbox); +} + +#endif /*LV_USE_MSGBOX*/ diff --git a/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.h b/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.h new file mode 100644 index 00000000..2eaf0d39 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.h @@ -0,0 +1,99 @@ +/** + * @file lv_mbox.h + * + */ + +#ifndef LV_MSGBOX_H +#define LV_MSGBOX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_MSGBOX + +/*Testing of dependencies*/ +#if LV_USE_BTNMATRIX == 0 +#error "lv_mbox: lv_btnm is required. Enable it in lv_conf.h (LV_USE_BTNMATRIX 1) " +#endif + +#if LV_USE_LABEL == 0 +#error "lv_mbox: lv_label is required. Enable it in lv_conf.h (LV_USE_LABEL 1) " +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_obj_t obj; + lv_obj_t * title; + lv_obj_t * close_btn; + lv_obj_t * content; + lv_obj_t * text; + lv_obj_t * btns; +} lv_msgbox_t; + +extern const lv_obj_class_t lv_msgbox_class; +extern const lv_obj_class_t lv_msgbox_content_class; +extern const lv_obj_class_t lv_msgbox_backdrop_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a message box object + * @param parent pointer to parent or NULL to create a full screen modal message box + * @param title the title of the message box + * @param txt the text of the message box + * @param btn_txts the buttons as an array of texts terminated by an "" element. E.g. {"btn1", "btn2", ""} + * @param add_close_btn true: add a close button + * @return pointer to the message box object + */ +lv_obj_t * lv_msgbox_create(lv_obj_t * parent, const char * title, const char * txt, const char * btn_txts[], + bool add_close_btn); + +lv_obj_t * lv_msgbox_get_title(lv_obj_t * obj); + +lv_obj_t * lv_msgbox_get_close_btn(lv_obj_t * obj); + +lv_obj_t * lv_msgbox_get_text(lv_obj_t * obj); + +lv_obj_t * lv_msgbox_get_content(lv_obj_t * obj); + +lv_obj_t * lv_msgbox_get_btns(lv_obj_t * obj); + +/** + * Get the index of the selected button + * @param mbox message box object + * @return index of the button (LV_BTNMATRIX_BTN_NONE: if unset) + */ +uint16_t lv_msgbox_get_active_btn(lv_obj_t * mbox); + +const char * lv_msgbox_get_active_btn_text(lv_obj_t * mbox); + +void lv_msgbox_close(lv_obj_t * mbox); + +void lv_msgbox_close_async(lv_obj_t * mbox); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_MSGBOX*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_MSGBOX_H*/ diff --git a/lib/lvgl/src/extra/widgets/span/lv_span.c b/lib/lvgl/src/extra/widgets/span/lv_span.c new file mode 100644 index 00000000..96f04476 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/span/lv_span.c @@ -0,0 +1,1041 @@ +/** + * @file lv_span.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_span.h" + +#if LV_USE_SPAN != 0 + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_spangroup_class + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_span_t * span; + const char * txt; + const lv_font_t * font; + uint16_t bytes; + lv_coord_t txt_w; + lv_coord_t line_h; + lv_coord_t letter_space; +} lv_snippet_t; + +struct _snippet_stack { + lv_snippet_t stack[LV_SPAN_SNIPPET_STACK_SIZE]; + uint16_t index; +}; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_spangroup_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_spangroup_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_spangroup_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void draw_main(lv_event_t * e); +static void refresh_self_size(lv_obj_t * obj); + +static const lv_font_t * lv_span_get_style_text_font(lv_obj_t * par, lv_span_t * span); +static lv_coord_t lv_span_get_style_text_letter_space(lv_obj_t * par, lv_span_t * span); +static lv_color_t lv_span_get_style_text_color(lv_obj_t * par, lv_span_t * span); +static lv_opa_t lv_span_get_style_text_opa(lv_obj_t * par, lv_span_t * span); +static lv_opa_t lv_span_get_style_text_blend_mode(lv_obj_t * par, lv_span_t * span); +static int32_t lv_span_get_style_text_decor(lv_obj_t * par, lv_span_t * span); + +static inline void span_text_check(const char ** text); +static void lv_draw_span(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static bool lv_txt_get_snippet(const char * txt, const lv_font_t * font, lv_coord_t letter_space, + lv_coord_t max_width, lv_text_flag_t flag, lv_coord_t * use_width, + uint32_t * end_ofs); + +static void lv_snippet_clear(void); +static uint16_t lv_get_snippet_cnt(void); +static void lv_snippet_push(lv_snippet_t * item); +static lv_snippet_t * lv_get_snippet(uint16_t index); +static lv_coord_t convert_indent_pct(lv_obj_t * spans, lv_coord_t width); + +/********************** + * STATIC VARIABLES + **********************/ +static struct _snippet_stack snippet_stack; + +const lv_obj_class_t lv_spangroup_class = { + .base_class = &lv_obj_class, + .constructor_cb = lv_spangroup_constructor, + .destructor_cb = lv_spangroup_destructor, + .event_cb = lv_spangroup_event, + .instance_size = sizeof(lv_spangroup_t), + .width_def = LV_SIZE_CONTENT, + .height_def = LV_SIZE_CONTENT, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_spangroup_create(lv_obj_t * par) +{ + lv_obj_t * obj = lv_obj_class_create_obj(&lv_spangroup_class, par); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_span_t * lv_spangroup_new_span(lv_obj_t * obj) +{ + if(obj == NULL) { + return NULL; + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + lv_span_t * span = _lv_ll_ins_tail(&spans->child_ll); + LV_ASSERT_MALLOC(span); + + lv_style_init(&span->style); + span->txt = (char *)""; + span->static_flag = 1; + span->spangroup = obj; + + refresh_self_size(obj); + + return span; +} + +void lv_spangroup_del_span(lv_obj_t * obj, lv_span_t * span) +{ + if(obj == NULL || span == NULL) { + return; + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + lv_span_t * cur_span; + _LV_LL_READ(&spans->child_ll, cur_span) { + if(cur_span == span) { + _lv_ll_remove(&spans->child_ll, cur_span); + if(cur_span->txt && cur_span->static_flag == 0) { + lv_mem_free(cur_span->txt); + } + lv_style_reset(&cur_span->style); + lv_mem_free(cur_span); + break; + } + } + + refresh_self_size(obj); +} + +/*===================== + * Setter functions + *====================*/ + +void lv_span_set_text(lv_span_t * span, const char * text) +{ + if(span == NULL || text == NULL) { + return; + } + + if(span->txt == NULL || span->static_flag == 1) { + span->txt = lv_mem_alloc(strlen(text) + 1); + } + else { + span->txt = lv_mem_realloc(span->txt, strlen(text) + 1); + } + span->static_flag = 0; + strcpy(span->txt, text); + + refresh_self_size(span->spangroup); +} + +void lv_span_set_text_static(lv_span_t * span, const char * text) +{ + if(span == NULL || text == NULL) { + return; + } + + if(span->txt && span->static_flag == 0) { + lv_mem_free(span->txt); + } + span->static_flag = 1; + span->txt = (char *)text; + + refresh_self_size(span->spangroup); +} + +void lv_spangroup_set_align(lv_obj_t * obj, lv_text_align_t align) +{ + lv_obj_set_style_text_align(obj, align, LV_PART_MAIN); +} + +void lv_spangroup_set_overflow(lv_obj_t * obj, lv_span_overflow_t overflow) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + if(spans->overflow == overflow) return; + + spans->overflow = overflow; + lv_obj_invalidate(obj); +} + +void lv_spangroup_set_indent(lv_obj_t * obj, lv_coord_t indent) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + if(spans->indent == indent) return; + + spans->indent = indent; + + refresh_self_size(obj); +} + +void lv_spangroup_set_mode(lv_obj_t * obj, lv_span_mode_t mode) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + spans->mode = mode; + lv_spangroup_refr_mode(obj); +} + +void lv_spangroup_set_lines(lv_obj_t * obj, int32_t lines) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + spans->lines = lines; + lv_spangroup_refr_mode(obj); +} + +/*===================== + * Getter functions + *====================*/ + +lv_span_t * lv_spangroup_get_child(const lv_obj_t * obj, int32_t id) +{ + if(obj == NULL) { + return NULL; + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + lv_ll_t * linked_list = &spans->child_ll; + + bool traverse_forwards = (id >= 0); + int32_t cur_idx = 0; + lv_ll_node_t * cur_node = linked_list->head; + + /*If using a negative index, start from the tail and use cur -1 to indicate the end*/ + if(!traverse_forwards) { + cur_idx = -1; + cur_node = linked_list->tail; + } + + while(cur_node != NULL) { + if(cur_idx == id) { + return (lv_span_t *) cur_node; + } + if(traverse_forwards) { + cur_node = (lv_ll_node_t *) _lv_ll_get_next(linked_list, cur_node); + cur_idx++; + } + else { + cur_node = (lv_ll_node_t *) _lv_ll_get_prev(linked_list, cur_node); + cur_idx--; + } + } + + return NULL; +} + +uint32_t lv_spangroup_get_child_cnt(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + if(obj == NULL) { + return 0; + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return _lv_ll_get_len(&(spans->child_ll)); +} + +lv_text_align_t lv_spangroup_get_align(lv_obj_t * obj) +{ + return lv_obj_get_style_text_align(obj, LV_PART_MAIN); +} + +lv_span_overflow_t lv_spangroup_get_overflow(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return spans->overflow; +} + +lv_coord_t lv_spangroup_get_indent(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return spans->indent; +} + +lv_span_mode_t lv_spangroup_get_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return spans->mode; +} + +int32_t lv_spangroup_get_lines(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return spans->lines; +} + +void lv_spangroup_refr_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + if(spans->mode == LV_SPAN_MODE_EXPAND) { + lv_obj_set_width(obj, LV_SIZE_CONTENT); + lv_obj_set_height(obj, LV_SIZE_CONTENT); + } + else if(spans->mode == LV_SPAN_MODE_BREAK) { + if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT) { + lv_obj_set_width(obj, 100); + } + lv_obj_set_height(obj, LV_SIZE_CONTENT); + } + else if(spans->mode == LV_SPAN_MODE_FIXED) { + /* use this mode, The user needs to set the size. */ + /* This is just to prevent an infinite loop. */ + if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT) { + lv_obj_set_width(obj, 100); + } + if(lv_obj_get_style_height(obj, LV_PART_MAIN) == LV_SIZE_CONTENT) { + lv_coord_t width = lv_obj_get_style_width(obj, LV_PART_MAIN); + if(LV_COORD_IS_PCT(width)) { + width = 100; + } + lv_coord_t height = lv_spangroup_get_expand_height(obj, width); + lv_obj_set_content_height(obj, height); + } + } + + refresh_self_size(obj); +} + +lv_coord_t lv_spangroup_get_max_line_h(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + lv_coord_t max_line_h = 0; + lv_span_t * cur_span; + _LV_LL_READ(&spans->child_ll, cur_span) { + const lv_font_t * font = lv_span_get_style_text_font(obj, cur_span); + lv_coord_t line_h = lv_font_get_line_height(font); + if(line_h > max_line_h) { + max_line_h = line_h; + } + } + + return max_line_h; +} + +uint32_t lv_spangroup_get_expand_width(lv_obj_t * obj, uint32_t max_width) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + if(_lv_ll_get_head(&spans->child_ll) == NULL) { + return 0; + } + + uint32_t width = LV_COORD_IS_PCT(spans->indent) ? 0 : spans->indent; + lv_span_t * cur_span; + lv_coord_t letter_space = 0; + _LV_LL_READ(&spans->child_ll, cur_span) { + const lv_font_t * font = lv_span_get_style_text_font(obj, cur_span); + letter_space = lv_span_get_style_text_letter_space(obj, cur_span); + uint32_t j = 0; + const char * cur_txt = cur_span->txt; + span_text_check(&cur_txt); + while(cur_txt[j] != '\0') { + if(max_width > 0 && width >= max_width) { + return max_width; + } + uint32_t letter = _lv_txt_encoded_next(cur_txt, &j); + uint32_t letter_next = _lv_txt_encoded_next(&cur_txt[j], NULL); + uint16_t letter_w = lv_font_get_glyph_width(font, letter, letter_next); + width = width + letter_w + letter_space; + } + } + + return width - letter_space; +} + +lv_coord_t lv_spangroup_get_expand_height(lv_obj_t * obj, lv_coord_t width) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + if(_lv_ll_get_head(&spans->child_ll) == NULL || width <= 0) { + return 0; + } + + /* init draw variable */ + lv_text_flag_t txt_flag = LV_TEXT_FLAG_NONE; + lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN); + lv_coord_t max_width = width; + lv_coord_t indent = convert_indent_pct(obj, max_width); + lv_coord_t max_w = max_width - indent; /* first line need minus indent */ + + /* coords of draw span-txt */ + lv_point_t txt_pos; + txt_pos.y = 0; + txt_pos.x = 0 + indent; /* first line need add indent */ + + lv_span_t * cur_span = _lv_ll_get_head(&spans->child_ll); + const char * cur_txt = cur_span->txt; + span_text_check(&cur_txt); + uint32_t cur_txt_ofs = 0; + lv_snippet_t snippet; /* use to save cur_span info and push it to stack */ + memset(&snippet, 0, sizeof(snippet)); + + int32_t line_cnt = 0; + int32_t lines = spans->lines < 0 ? INT32_MAX : spans->lines; + /* the loop control how many lines need to draw */ + while(cur_span) { + int snippet_cnt = 0; + lv_coord_t max_line_h = 0; /* the max height of span-font when a line have a lot of span */ + + /* the loop control to find a line and push the relevant span info into stack */ + while(1) { + /* switch to the next span when current is end */ + if(cur_txt[cur_txt_ofs] == '\0') { + cur_span = _lv_ll_get_next(&spans->child_ll, cur_span); + if(cur_span == NULL) break; + cur_txt = cur_span->txt; + span_text_check(&cur_txt); + cur_txt_ofs = 0; + /* maybe also cur_txt[cur_txt_ofs] == '\0' */ + continue; + } + + /* init span info to snippet. */ + if(cur_txt_ofs == 0) { + snippet.span = cur_span; + snippet.font = lv_span_get_style_text_font(obj, cur_span); + snippet.letter_space = lv_span_get_style_text_letter_space(obj, cur_span); + snippet.line_h = lv_font_get_line_height(snippet.font) + line_space; + } + + /* get current span text line info */ + uint32_t next_ofs = 0; + lv_coord_t use_width = 0; + bool isfill = lv_txt_get_snippet(&cur_txt[cur_txt_ofs], snippet.font, snippet.letter_space, + max_w, txt_flag, &use_width, &next_ofs); + + /* break word deal width */ + if(isfill && next_ofs > 0 && snippet_cnt > 0) { + if(max_w < use_width) { + break; + } + + uint32_t tmp_ofs = next_ofs; + uint32_t letter = _lv_txt_encoded_prev(&cur_txt[cur_txt_ofs], &tmp_ofs); + if(!(letter == '\0' || letter == '\n' || letter == '\r' || _lv_txt_is_break_char(letter))) { + tmp_ofs = 0; + letter = _lv_txt_encoded_next(&cur_txt[cur_txt_ofs + next_ofs], &tmp_ofs); + if(!(letter == '\0' || letter == '\n' || letter == '\r' || _lv_txt_is_break_char(letter))) { + break; + } + } + } + + snippet.txt = &cur_txt[cur_txt_ofs]; + snippet.bytes = next_ofs; + snippet.txt_w = use_width; + cur_txt_ofs += next_ofs; + if(max_line_h < snippet.line_h) { + max_line_h = snippet.line_h; + } + snippet_cnt ++; + max_w = max_w - use_width - snippet.letter_space; + if(isfill || max_w <= 0) { + break; + } + } + + /* next line init */ + txt_pos.x = 0; + txt_pos.y += max_line_h; + max_w = max_width; + line_cnt += 1; + if(line_cnt >= lines) { + break; + } + } + txt_pos.y -= line_space; + + return txt_pos.y; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_spangroup_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + _lv_ll_init(&spans->child_ll, sizeof(lv_span_t)); + spans->indent = 0; + spans->lines = -1; + spans->mode = LV_SPAN_MODE_EXPAND; + spans->overflow = LV_SPAN_OVERFLOW_CLIP; + spans->cache_w = 0; + spans->cache_h = 0; + spans->refresh = 1; +} + +static void lv_spangroup_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + lv_span_t * cur_span = _lv_ll_get_head(&spans->child_ll); + while(cur_span) { + _lv_ll_remove(&spans->child_ll, cur_span); + if(cur_span->txt && cur_span->static_flag == 0) { + lv_mem_free(cur_span->txt); + } + lv_style_reset(&cur_span->style); + lv_mem_free(cur_span); + cur_span = _lv_ll_get_head(&spans->child_ll); + } +} + +static void lv_spangroup_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + /* Call the ancestor's event handler */ + if(lv_obj_event_base(MY_CLASS, e) != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + if(code == LV_EVENT_DRAW_MAIN) { + draw_main(e); + } + else if(code == LV_EVENT_STYLE_CHANGED) { + refresh_self_size(obj); + } + else if(code == LV_EVENT_SIZE_CHANGED) { + refresh_self_size(obj); + } + else if(code == LV_EVENT_GET_SELF_SIZE) { + lv_coord_t width = 0; + lv_coord_t height = 0; + lv_point_t * self_size = lv_event_get_param(e); + + if(spans->mode == LV_SPAN_MODE_EXPAND) { + if(spans->refresh) { + spans->cache_w = (lv_coord_t)lv_spangroup_get_expand_width(obj, 0); + spans->cache_h = lv_spangroup_get_max_line_h(obj); + spans->refresh = 0; + } + width = spans->cache_w; + height = spans->cache_h; + } + else if(spans->mode == LV_SPAN_MODE_BREAK) { + width = lv_obj_get_content_width(obj); + if(self_size->y >= 0) { + if(width != spans->cache_w || spans->refresh) { + height = lv_spangroup_get_expand_height(obj, width); + spans->cache_w = width; + spans->cache_h = height; + spans->refresh = 0; + } + else { + height = spans->cache_h; + } + } + } + else if(spans->mode == LV_SPAN_MODE_FIXED) { + width = self_size->x >= 0 ? lv_obj_get_content_width(obj) : 0; + height = self_size->y >= 0 ? lv_obj_get_content_height(obj) : 0; + } + self_size->x = LV_MAX(self_size->x, width); + self_size->y = LV_MAX(self_size->y, height); + } +} + +static void draw_main(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + + lv_draw_span(obj, draw_ctx); +} + +/** + * @return true for txt fill the max_width. + */ +static bool lv_txt_get_snippet(const char * txt, const lv_font_t * font, + lv_coord_t letter_space, lv_coord_t max_width, lv_text_flag_t flag, + lv_coord_t * use_width, uint32_t * end_ofs) +{ + if(txt == NULL || txt[0] == '\0') { + *end_ofs = 0; + *use_width = 0; + return false; + } + + uint32_t ofs = _lv_txt_get_next_line(txt, font, letter_space, max_width, use_width, flag); + *end_ofs = ofs; + + if(txt[ofs] == '\0' && *use_width < max_width) { + return false; + } + else { + return true; + } +} + +static void lv_snippet_push(lv_snippet_t * item) +{ + if(snippet_stack.index < LV_SPAN_SNIPPET_STACK_SIZE) { + memcpy(&snippet_stack.stack[snippet_stack.index], item, sizeof(lv_snippet_t)); + snippet_stack.index++; + } + else { + LV_LOG_ERROR("span draw stack overflow, please set LV_SPAN_SNIPPET_STACK_SIZE too larger"); + } +} + +static uint16_t lv_get_snippet_cnt(void) +{ + return snippet_stack.index; +} + +static lv_snippet_t * lv_get_snippet(uint16_t index) +{ + return &snippet_stack.stack[index]; +} + +static void lv_snippet_clear(void) +{ + snippet_stack.index = 0; +} + +static const lv_font_t * lv_span_get_style_text_font(lv_obj_t * par, lv_span_t * span) +{ + const lv_font_t * font; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_FONT, &value); + if(res != LV_RES_OK) { + font = lv_obj_get_style_text_font(par, LV_PART_MAIN); + } + else { + font = (const lv_font_t *)value.ptr; + } + return font; +} + +static lv_coord_t lv_span_get_style_text_letter_space(lv_obj_t * par, lv_span_t * span) +{ + lv_coord_t letter_space; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_LETTER_SPACE, &value); + if(res != LV_RES_OK) { + letter_space = lv_obj_get_style_text_letter_space(par, LV_PART_MAIN); + } + else { + letter_space = (lv_coord_t)value.num; + } + return letter_space; +} + +static lv_color_t lv_span_get_style_text_color(lv_obj_t * par, lv_span_t * span) +{ + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_COLOR, &value); + if(res != LV_RES_OK) { + value.color = lv_obj_get_style_text_color(par, LV_PART_MAIN); + } + return value.color; +} + +static lv_opa_t lv_span_get_style_text_opa(lv_obj_t * par, lv_span_t * span) +{ + lv_opa_t opa; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_OPA, &value); + if(res != LV_RES_OK) { + opa = (lv_opa_t)lv_obj_get_style_text_opa(par, LV_PART_MAIN); + } + else { + opa = (lv_opa_t)value.num; + } + return opa; +} + +static lv_blend_mode_t lv_span_get_style_text_blend_mode(lv_obj_t * par, lv_span_t * span) +{ + lv_blend_mode_t mode; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_BLEND_MODE, &value); + if(res != LV_RES_OK) { + mode = (lv_blend_mode_t)lv_obj_get_style_blend_mode(par, LV_PART_MAIN); + } + else { + mode = (lv_blend_mode_t)value.num; + } + return mode; +} + +static int32_t lv_span_get_style_text_decor(lv_obj_t * par, lv_span_t * span) +{ + int32_t decor; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_DECOR, &value); + if(res != LV_RES_OK) { + decor = (lv_text_decor_t)lv_obj_get_style_text_decor(par, LV_PART_MAIN);; + } + else { + decor = (int32_t)value.num; + } + return decor; +} + +static inline void span_text_check(const char ** text) +{ + if(*text == NULL) { + *text = ""; + LV_LOG_ERROR("occur an error that span text == NULL"); + } +} + +static lv_coord_t convert_indent_pct(lv_obj_t * obj, lv_coord_t width) +{ + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + lv_coord_t indent = spans->indent; + if(LV_COORD_IS_PCT(spans->indent)) { + if(spans->mode == LV_SPAN_MODE_EXPAND) { + indent = 0; + } + else { + indent = (width * LV_COORD_GET_PCT(spans->indent)) / 100; + } + } + + return indent; +} + +/** + * draw span group + * @param spans obj handle + * @param coords coordinates of the label + * @param mask the label will be drawn only in this area + */ +static void lv_draw_span(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + + lv_area_t coords; + lv_obj_get_content_coords(obj, &coords); + + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + /* return if not span */ + if(_lv_ll_get_head(&spans->child_ll) == NULL) { + return; + } + + /* return if no draw area */ + lv_area_t clip_area; + if(!_lv_area_intersect(&clip_area, &coords, draw_ctx->clip_area)) return; + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + /* init draw variable */ + lv_text_flag_t txt_flag = LV_TEXT_FLAG_NONE; + lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);; + lv_coord_t max_width = lv_area_get_width(&coords); + lv_coord_t indent = convert_indent_pct(obj, max_width); + lv_coord_t max_w = max_width - indent; /* first line need minus indent */ + lv_opa_t obj_opa = lv_obj_get_style_opa(obj, LV_PART_MAIN); + + /* coords of draw span-txt */ + lv_point_t txt_pos; + txt_pos.y = coords.y1; + txt_pos.x = coords.x1 + indent; /* first line need add indent */ + + lv_span_t * cur_span = _lv_ll_get_head(&spans->child_ll); + const char * cur_txt = cur_span->txt; + span_text_check(&cur_txt); + uint32_t cur_txt_ofs = 0; + lv_snippet_t snippet; /* use to save cur_span info and push it to stack */ + lv_memset_00(&snippet, sizeof(snippet)); + + lv_draw_label_dsc_t label_draw_dsc; + lv_draw_label_dsc_init(&label_draw_dsc); + + bool is_first_line = true; + /* the loop control how many lines need to draw */ + while(cur_span) { + bool is_end_line = false; + bool ellipsis_valid = false; + lv_coord_t max_line_h = 0; /* the max height of span-font when a line have a lot of span */ + lv_coord_t max_baseline = 0; /*baseline of the highest span*/ + lv_snippet_clear(); + + /* the loop control to find a line and push the relevant span info into stack */ + while(1) { + /* switch to the next span when current is end */ + if(cur_txt[cur_txt_ofs] == '\0') { + cur_span = _lv_ll_get_next(&spans->child_ll, cur_span); + if(cur_span == NULL) break; + cur_txt = cur_span->txt; + span_text_check(&cur_txt); + cur_txt_ofs = 0; + /* maybe also cur_txt[cur_txt_ofs] == '\0' */ + continue; + } + + /* init span info to snippet. */ + if(cur_txt_ofs == 0) { + snippet.span = cur_span; + snippet.font = lv_span_get_style_text_font(obj, cur_span); + snippet.letter_space = lv_span_get_style_text_letter_space(obj, cur_span); + snippet.line_h = lv_font_get_line_height(snippet.font) + line_space; + } + + /* get current span text line info */ + uint32_t next_ofs = 0; + lv_coord_t use_width = 0; + bool isfill = lv_txt_get_snippet(&cur_txt[cur_txt_ofs], snippet.font, snippet.letter_space, + max_w, txt_flag, &use_width, &next_ofs); + + if(isfill) { + if(next_ofs > 0 && lv_get_snippet_cnt() > 0) { + /* To prevent infinite loops, the _lv_txt_get_next_line() may return incomplete words, */ + /* This phenomenon should be avoided when lv_get_snippet_cnt() > 0 */ + if(max_w < use_width) { + break; + } + uint32_t tmp_ofs = next_ofs; + uint32_t letter = _lv_txt_encoded_prev(&cur_txt[cur_txt_ofs], &tmp_ofs); + if(!(letter == '\0' || letter == '\n' || letter == '\r' || _lv_txt_is_break_char(letter))) { + tmp_ofs = 0; + letter = _lv_txt_encoded_next(&cur_txt[cur_txt_ofs + next_ofs], &tmp_ofs); + if(!(letter == '\0' || letter == '\n' || letter == '\r' || _lv_txt_is_break_char(letter))) { + break; + } + } + } + } + + snippet.txt = &cur_txt[cur_txt_ofs]; + snippet.bytes = next_ofs; + snippet.txt_w = use_width; + cur_txt_ofs += next_ofs; + if(max_line_h < snippet.line_h) { + max_line_h = snippet.line_h; + max_baseline = snippet.font->base_line; + } + + lv_snippet_push(&snippet); + max_w = max_w - use_width - snippet.letter_space; + if(isfill || max_w <= 0) { + break; + } + } + + /* start current line deal with */ + + uint16_t item_cnt = lv_get_snippet_cnt(); + if(item_cnt == 0) { /* break if stack is empty */ + break; + } + + /* Whether the current line is the end line and does overflow processing */ + { + lv_snippet_t * last_snippet = lv_get_snippet(item_cnt - 1); + lv_coord_t next_line_h = last_snippet->line_h; + if(last_snippet->txt[last_snippet->bytes] == '\0') { + next_line_h = 0; + lv_span_t * next_span = _lv_ll_get_next(&spans->child_ll, last_snippet->span); + if(next_span) { /* have the next line */ + next_line_h = lv_font_get_line_height(lv_span_get_style_text_font(obj, next_span)) + line_space; + } + } + if(txt_pos.y + max_line_h + next_line_h - line_space > coords.y2 + 1) { /* for overflow if is end line. */ + if(last_snippet->txt[last_snippet->bytes] != '\0') { + last_snippet->bytes = strlen(last_snippet->txt); + last_snippet->txt_w = lv_txt_get_width(last_snippet->txt, last_snippet->bytes, last_snippet->font, + last_snippet->letter_space, txt_flag); + } + ellipsis_valid = spans->overflow == LV_SPAN_OVERFLOW_ELLIPSIS ? true : false; + is_end_line = true; + } + } + + /*Go the first visible line*/ + if(txt_pos.y + max_line_h < clip_area.y1) { + goto Next_line_init; + } + + /* align deal with */ + lv_text_align_t align = lv_obj_get_style_text_align(obj, LV_PART_MAIN); + if(align == LV_TEXT_ALIGN_CENTER || align == LV_TEXT_ALIGN_RIGHT) { + lv_coord_t align_ofs = 0; + lv_coord_t txts_w = is_first_line ? indent : 0; + for(int i = 0; i < item_cnt; i++) { + lv_snippet_t * pinfo = lv_get_snippet(i); + txts_w = txts_w + pinfo->txt_w + pinfo->letter_space; + } + txts_w -= lv_get_snippet(item_cnt - 1)->letter_space; + align_ofs = max_width > txts_w ? max_width - txts_w : 0; + if(align == LV_TEXT_ALIGN_CENTER) { + align_ofs = align_ofs >> 1; + } + txt_pos.x += align_ofs; + } + + /* draw line letters */ + int i; + for(i = 0; i < item_cnt; i++) { + lv_snippet_t * pinfo = lv_get_snippet(i); + + /* bidi deal with:todo */ + const char * bidi_txt = pinfo->txt; + + lv_point_t pos; + pos.x = txt_pos.x; + pos.y = txt_pos.y + max_line_h - pinfo->line_h - (max_baseline - pinfo->font->base_line); + label_draw_dsc.color = lv_span_get_style_text_color(obj, pinfo->span); + label_draw_dsc.opa = lv_span_get_style_text_opa(obj, pinfo->span); + label_draw_dsc.font = lv_span_get_style_text_font(obj, pinfo->span); + label_draw_dsc.blend_mode = lv_span_get_style_text_blend_mode(obj, pinfo->span); + if(obj_opa < LV_OPA_MAX) { + label_draw_dsc.opa = (uint16_t)((uint16_t)label_draw_dsc.opa * obj_opa) >> 8; + } + uint32_t txt_bytes = pinfo->bytes; + + /* overflow */ + uint16_t dot_letter_w = 0; + uint16_t dot_width = 0; + if(ellipsis_valid) { + dot_letter_w = lv_font_get_glyph_width(pinfo->font, '.', '.'); + dot_width = dot_letter_w * 3; + } + lv_coord_t ellipsis_width = coords.x1 + max_width - dot_width; + + uint32_t j = 0; + while(j < txt_bytes) { + /* skip invalid fields */ + if(pos.x > clip_area.x2) { + break; + } + uint32_t letter = _lv_txt_encoded_next(bidi_txt, &j); + uint32_t letter_next = _lv_txt_encoded_next(&bidi_txt[j], NULL); + int32_t letter_w = lv_font_get_glyph_width(pinfo->font, letter, letter_next); + + /* skip invalid fields */ + if(pos.x + letter_w + pinfo->letter_space < clip_area.x1) { + if(letter_w > 0) { + pos.x = pos.x + letter_w + pinfo->letter_space; + } + continue; + } + + if(ellipsis_valid && pos.x + letter_w + pinfo->letter_space > ellipsis_width) { + for(int ell = 0; ell < 3; ell++) { + lv_draw_letter(draw_ctx, &label_draw_dsc, &pos, '.'); + pos.x = pos.x + dot_letter_w + pinfo->letter_space; + } + if(pos.x <= ellipsis_width) { + pos.x = ellipsis_width + 1; + } + break; + } + else { + lv_draw_letter(draw_ctx, &label_draw_dsc, &pos, letter); + if(letter_w > 0) { + pos.x = pos.x + letter_w + pinfo->letter_space; + } + } + } + + /* draw decor */ + lv_text_decor_t decor = lv_span_get_style_text_decor(obj, pinfo->span); + if(decor != LV_TEXT_DECOR_NONE) { + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + line_dsc.color = label_draw_dsc.color; + line_dsc.width = label_draw_dsc.font->underline_thickness ? pinfo->font->underline_thickness : 1; + line_dsc.opa = label_draw_dsc.opa; + line_dsc.blend_mode = label_draw_dsc.blend_mode; + + if(decor & LV_TEXT_DECOR_STRIKETHROUGH) { + lv_point_t p1; + lv_point_t p2; + p1.x = txt_pos.x; + p1.y = pos.y + ((pinfo->line_h - line_space) >> 1) + (line_dsc.width >> 1); + p2.x = pos.x; + p2.y = p1.y; + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + } + + if(decor & LV_TEXT_DECOR_UNDERLINE) { + lv_point_t p1; + lv_point_t p2; + p1.x = txt_pos.x; + p1.y = pos.y + pinfo->line_h - line_space - pinfo->font->base_line - pinfo->font->underline_position; + p2.x = pos.x; + p2.y = p1.y; + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + } + } + txt_pos.x = pos.x; + } + +Next_line_init: + /* next line init */ + is_first_line = false; + txt_pos.x = coords.x1; + txt_pos.y += max_line_h; + if(is_end_line || txt_pos.y > clip_area.y2 + 1) { + draw_ctx->clip_area = clip_area_ori; + return; + } + max_w = max_width; + } + draw_ctx->clip_area = clip_area_ori; +} + +static void refresh_self_size(lv_obj_t * obj) +{ + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + spans->refresh = 1; + lv_obj_invalidate(obj); + lv_obj_refresh_self_size(obj); +} + +#endif diff --git a/lib/lvgl/src/extra/widgets/span/lv_span.h b/lib/lvgl/src/extra/widgets/span/lv_span.h new file mode 100644 index 00000000..f00d04db --- /dev/null +++ b/lib/lvgl/src/extra/widgets/span/lv_span.h @@ -0,0 +1,245 @@ +/** + * @file lv_span.h + * + */ + +#ifndef LV_SPAN_H +#define LV_SPAN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_SPAN != 0 + +/********************* + * DEFINES + *********************/ +#ifndef LV_SPAN_SNIPPET_STACK_SIZE +#define LV_SPAN_SNIPPET_STACK_SIZE 64 +#endif + +/********************** + * TYPEDEFS + **********************/ +enum { + LV_SPAN_OVERFLOW_CLIP, + LV_SPAN_OVERFLOW_ELLIPSIS, +}; +typedef uint8_t lv_span_overflow_t; + +enum { + LV_SPAN_MODE_FIXED, /**< fixed the obj size*/ + LV_SPAN_MODE_EXPAND, /**< Expand the object size to the text size*/ + LV_SPAN_MODE_BREAK, /**< Keep width, break the too long lines and expand height*/ +}; +typedef uint8_t lv_span_mode_t; + +typedef struct { + char * txt; /* a pointer to display text */ + lv_obj_t * spangroup; /* a pointer to spangroup */ + lv_style_t style; /* display text style */ + uint8_t static_flag : 1;/* the text is static flag */ +} lv_span_t; + +/** Data of label*/ +typedef struct { + lv_obj_t obj; + int32_t lines; + lv_coord_t indent; /* first line indent */ + lv_coord_t cache_w; /* the cache automatically calculates the width */ + lv_coord_t cache_h; /* similar cache_w */ + lv_ll_t child_ll; + uint8_t mode : 2; /* details see lv_span_mode_t */ + uint8_t overflow : 1; /* details see lv_span_overflow_t */ + uint8_t refresh : 1; /* the spangroup need refresh cache_w and cache_h */ +} lv_spangroup_t; + +extern const lv_obj_class_t lv_spangroup_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a spangroup object + * @param par pointer to an object, it will be the parent of the new spangroup + * @return pointer to the created spangroup + */ +lv_obj_t * lv_spangroup_create(lv_obj_t * par); + +/** + * Create a span string descriptor and add to spangroup. + * @param obj pointer to a spangroup object. + * @return pointer to the created span. + */ +lv_span_t * lv_spangroup_new_span(lv_obj_t * obj); + +/** + * Remove the span from the spangroup and free memory. + * @param obj pointer to a spangroup object. + * @param span pointer to a span. + */ +void lv_spangroup_del_span(lv_obj_t * obj, lv_span_t * span); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set a new text for a span. Memory will be allocated to store the text by the span. + * @param span pointer to a span. + * @param text pointer to a text. + */ +void lv_span_set_text(lv_span_t * span, const char * text); + +/** + * Set a static text. It will not be saved by the span so the 'text' variable + * has to be 'alive' while the span exist. + * @param span pointer to a span. + * @param text pointer to a text. + */ +void lv_span_set_text_static(lv_span_t * span, const char * text); + +/** + * Set the align of the spangroup. + * @param obj pointer to a spangroup object. + * @param align see lv_text_align_t for details. + */ +void lv_spangroup_set_align(lv_obj_t * obj, lv_text_align_t align); + +/** + * Set the overflow of the spangroup. + * @param obj pointer to a spangroup object. + * @param overflow see lv_span_overflow_t for details. + */ +void lv_spangroup_set_overflow(lv_obj_t * obj, lv_span_overflow_t overflow); + +/** + * Set the indent of the spangroup. + * @param obj pointer to a spangroup object. + * @param indent The first line indentation + */ +void lv_spangroup_set_indent(lv_obj_t * obj, lv_coord_t indent); + +/** + * Set the mode of the spangroup. + * @param obj pointer to a spangroup object. + * @param mode see lv_span_mode_t for details. + */ +void lv_spangroup_set_mode(lv_obj_t * obj, lv_span_mode_t mode); + +/** + * Set lines of the spangroup. + * @param obj pointer to a spangroup object. + * @param lines max lines that can be displayed in LV_SPAN_MODE_BREAK mode. < 0 means no limit. + */ +void lv_spangroup_set_lines(lv_obj_t * obj, int32_t lines); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get a spangroup child by its index. + * + * @param obj The spangroup object + * @param id the index of the child. + * 0: the oldest (firstly created) child + * 1: the second oldest + * child count-1: the youngest + * -1: the youngest + * -2: the second youngest + * @return The child span at index `id`, or NULL if the ID does not exist + */ +lv_span_t * lv_spangroup_get_child(const lv_obj_t * obj, int32_t id); + +/** + * + * @param obj The spangroup object to get the child count of. + * @return The span count of the spangroup. + */ +uint32_t lv_spangroup_get_child_cnt(const lv_obj_t * obj); + +/** + * get the align of the spangroup. + * @param obj pointer to a spangroup object. + * @return the align value. + */ +lv_text_align_t lv_spangroup_get_align(lv_obj_t * obj); + +/** + * get the overflow of the spangroup. + * @param obj pointer to a spangroup object. + * @return the overflow value. + */ +lv_span_overflow_t lv_spangroup_get_overflow(lv_obj_t * obj); + +/** + * get the indent of the spangroup. + * @param obj pointer to a spangroup object. + * @return the indent value. + */ +lv_coord_t lv_spangroup_get_indent(lv_obj_t * obj); + +/** + * get the mode of the spangroup. + * @param obj pointer to a spangroup object. + */ +lv_span_mode_t lv_spangroup_get_mode(lv_obj_t * obj); + +/** + * get lines of the spangroup. + * @param obj pointer to a spangroup object. + * @return the lines value. + */ +int32_t lv_spangroup_get_lines(lv_obj_t * obj); + +/** + * get max line height of all span in the spangroup. + * @param obj pointer to a spangroup object. + */ +lv_coord_t lv_spangroup_get_max_line_h(lv_obj_t * obj); + +/** + * get the text content width when all span of spangroup on a line. + * @param obj pointer to a spangroup object. + * @param max_width if text content width >= max_width, return max_width + * to reduce computation, if max_width == 0, returns the text content width. + * @return text content width or max_width. + */ +uint32_t lv_spangroup_get_expand_width(lv_obj_t * obj, uint32_t max_width); + +/** + * get the text content height with width fixed. + * @param obj pointer to a spangroup object. + */ +lv_coord_t lv_spangroup_get_expand_height(lv_obj_t * obj, lv_coord_t width); + + +/*===================== + * Other functions + *====================*/ + +/** + * update the mode of the spangroup. + * @param obj pointer to a spangroup object. + */ +void lv_spangroup_refr_mode(lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_SPAN*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_SPAN_H*/ diff --git a/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.c b/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.c new file mode 100644 index 00000000..34691053 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.c @@ -0,0 +1,516 @@ +/** + * @file lv_spinbox.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_spinbox.h" +#if LV_USE_SPINBOX + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_spinbox_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void lv_spinbox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_spinbox_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void lv_spinbox_updatevalue(lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_spinbox_class = { + .constructor_cb = lv_spinbox_constructor, + .event_cb = lv_spinbox_event, + .width_def = LV_DPI_DEF, + .instance_size = sizeof(lv_spinbox_t), + .editable = LV_OBJ_CLASS_EDITABLE_TRUE, + .base_class = &lv_textarea_class +}; +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_spinbox_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Set spinbox value + * @param obj pointer to spinbox + * @param i value to be set + */ +void lv_spinbox_set_value(lv_obj_t * obj, int32_t i) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + if(i > spinbox->range_max) i = spinbox->range_max; + if(i < spinbox->range_min) i = spinbox->range_min; + + spinbox->value = i; + + lv_spinbox_updatevalue(obj); +} + +/** + * Set spinbox rollover function + * @param spinbox pointer to spinbox + * @param b true or false to enable or disable (default) + */ +void lv_spinbox_set_rollover(lv_obj_t * obj, bool b) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + spinbox->rollover = b; +} + +/** + * Set spinbox digit format (digit count and decimal format) + * @param spinbox pointer to spinbox + * @param digit_count number of digit excluding the decimal separator and the sign + * @param separator_position number of digit before the decimal point. If 0, decimal point is not + * shown + */ +void lv_spinbox_set_digit_format(lv_obj_t * obj, uint8_t digit_count, uint8_t separator_position) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + if(digit_count > LV_SPINBOX_MAX_DIGIT_COUNT) digit_count = LV_SPINBOX_MAX_DIGIT_COUNT; + + if(separator_position >= digit_count) separator_position = 0; + + if(digit_count < LV_SPINBOX_MAX_DIGIT_COUNT) { + int64_t max_val = lv_pow(10, digit_count); + if(spinbox->range_max > max_val - 1) spinbox->range_max = max_val - 1; + if(spinbox->range_min < - max_val + 1) spinbox->range_min = - max_val + 1; + } + + spinbox->digit_count = digit_count; + spinbox->dec_point_pos = separator_position; + + lv_spinbox_updatevalue(obj); +} + +/** + * Set spinbox step + * @param spinbox pointer to spinbox + * @param step steps on increment/decrement + */ +void lv_spinbox_set_step(lv_obj_t * obj, uint32_t step) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + spinbox->step = step; + lv_spinbox_updatevalue(obj); +} + +/** + * Set spinbox value range + * @param spinbox pointer to spinbox + * @param range_min maximum value, inclusive + * @param range_max minimum value, inclusive + */ +void lv_spinbox_set_range(lv_obj_t * obj, int32_t range_min, int32_t range_max) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + spinbox->range_max = range_max; + spinbox->range_min = range_min; + + if(spinbox->value > spinbox->range_max) spinbox->value = spinbox->range_max; + if(spinbox->value < spinbox->range_min) spinbox->value = spinbox->range_min; + + lv_spinbox_updatevalue(obj); +} + +/** + * Set cursor position to a specific digit for edition + * @param spinbox pointer to spinbox + * @param pos selected position in spinbox + */ +void lv_spinbox_set_cursor_pos(lv_obj_t * obj, uint8_t pos) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + int32_t step_limit; + step_limit = LV_MAX(spinbox->range_max, (spinbox->range_min < 0 ? (-spinbox->range_min) : spinbox->range_min)); + int32_t new_step = spinbox->step * lv_pow(10, pos); + if(pos <= 0) spinbox->step = 1; + else if(new_step <= step_limit) spinbox->step = new_step; + + lv_spinbox_updatevalue(obj); +} + +/** + * Set direction of digit step when clicking an encoder button while in editing mode + * @param spinbox pointer to spinbox + * @param direction the direction (LV_DIR_RIGHT or LV_DIR_LEFT) + */ +void lv_spinbox_set_digit_step_direction(lv_obj_t * obj, lv_dir_t direction) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + spinbox->digit_step_dir = direction; + + lv_spinbox_updatevalue(obj); +} +/*===================== + * Getter functions + *====================*/ + +/** + * Get the spinbox numeral value (user has to convert to float according to its digit format) + * @param obj pointer to spinbox + * @return value integer value of the spinbox + */ +int32_t lv_spinbox_get_value(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + return spinbox->value; +} +/** + * Get the spinbox step value (user has to convert to float according to its digit format) + * @param obj pointer to spinbox + * @return value integer step value of the spinbox + */ +int32_t lv_spinbox_get_step(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + return spinbox->step; +} + +/*===================== + * Other functions + *====================*/ + +/** + * Select next lower digit for edition + * @param obj pointer to spinbox + */ +void lv_spinbox_step_next(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + int32_t new_step = spinbox->step / 10; + if((new_step) > 0) + spinbox->step = new_step; + else + spinbox->step = 1; + + lv_spinbox_updatevalue(obj); +} + +/** + * Select next higher digit for edition + * @param obj pointer to spinbox + */ +void lv_spinbox_step_prev(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + int32_t step_limit; + step_limit = LV_MAX(spinbox->range_max, (spinbox->range_min < 0 ? (-spinbox->range_min) : spinbox->range_min)); + int32_t new_step = spinbox->step * 10; + if(new_step <= step_limit) spinbox->step = new_step; + + lv_spinbox_updatevalue(obj); +} + +/** + * Get spinbox rollover function status + * @param obj pointer to spinbox + */ +bool lv_spinbox_get_rollover(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + return spinbox->rollover; +} + +/** + * Increment spinbox value by one step + * @param obj pointer to spinbox + */ +void lv_spinbox_increment(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + if(spinbox->value + spinbox->step <= spinbox->range_max) { + /*Special mode when zero crossing*/ + if((spinbox->value + spinbox->step) > 0 && spinbox->value < 0) spinbox->value = -spinbox->value; + spinbox->value += spinbox->step; + + } + else { + // Rollover? + if((spinbox->rollover) && (spinbox->value == spinbox->range_max)) + spinbox->value = spinbox->range_min; + else + spinbox->value = spinbox->range_max; + } + + lv_spinbox_updatevalue(obj); +} + +/** + * Decrement spinbox value by one step + * @param obj pointer to spinbox + */ +void lv_spinbox_decrement(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + if(spinbox->value - spinbox->step >= spinbox->range_min) { + /*Special mode when zero crossing*/ + if((spinbox->value - spinbox->step) < 0 && spinbox->value > 0) spinbox->value = -spinbox->value; + spinbox->value -= spinbox->step; + } + else { + /*Rollover?*/ + if((spinbox->rollover) && (spinbox->value == spinbox->range_min)) + spinbox->value = spinbox->range_max; + else + spinbox->value = spinbox->range_min; + } + + lv_spinbox_updatevalue(obj); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_spinbox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_LOG_TRACE("begin"); + + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + /*Initialize the allocated 'ext'*/ + spinbox->value = 0; + spinbox->dec_point_pos = 0; + spinbox->digit_count = 5; + spinbox->step = 1; + spinbox->range_max = 99999; + spinbox->range_min = -99999; + spinbox->rollover = false; + spinbox->digit_step_dir = LV_DIR_RIGHT; + + lv_textarea_set_one_line(obj, true); + lv_textarea_set_cursor_click_pos(obj, true); + + lv_spinbox_updatevalue(obj); + + LV_LOG_TRACE("Spinbox constructor finished"); +} + +static void lv_spinbox_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + /*Call the ancestor's event handler*/ + lv_res_t res = LV_RES_OK; + res = lv_obj_event_base(MY_CLASS, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + if(code == LV_EVENT_RELEASED) { + /*If released with an ENCODER then move to the next digit*/ + lv_indev_t * indev = lv_indev_get_act(); + if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) { + if(lv_group_get_editing(lv_obj_get_group(obj))) { + if(spinbox->digit_count > 1) { + if(spinbox->digit_step_dir == LV_DIR_RIGHT) { + if(spinbox->step > 1) { + lv_spinbox_step_next(obj); + } + else { + /*Restart from the MSB*/ + spinbox->step = lv_pow(10, spinbox->digit_count - 2); + lv_spinbox_step_prev(obj); + } + } + else { + if(spinbox->step < lv_pow(10, spinbox->digit_count - 1)) { + lv_spinbox_step_prev(obj); + } + else { + /*Restart from the LSB*/ + spinbox->step = 10; + lv_spinbox_step_next(obj); + } + } + } + } + } + /*The cursor has been positioned to a digit. + * Set `step` accordingly*/ + else { + const char * txt = lv_textarea_get_text(obj); + size_t txt_len = strlen(txt); + + if(txt[spinbox->ta.cursor.pos] == '.') { + lv_textarea_cursor_left(obj); + } + else if(spinbox->ta.cursor.pos == (uint32_t)txt_len) { + lv_textarea_set_cursor_pos(obj, txt_len - 1); + } + else if(spinbox->ta.cursor.pos == 0 && spinbox->range_min < 0) { + lv_textarea_set_cursor_pos(obj, 1); + } + + size_t len = spinbox->digit_count - 1; + uint16_t cp = spinbox->ta.cursor.pos; + + if(spinbox->ta.cursor.pos > spinbox->dec_point_pos && spinbox->dec_point_pos != 0) cp--; + uint32_t pos = len - cp; + + if(spinbox->range_min < 0) pos++; + + spinbox->step = 1; + uint16_t i; + for(i = 0; i < pos; i++) spinbox->step *= 10; + } + } + else if(code == LV_EVENT_KEY) { + lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act()); + + uint32_t c = *((uint32_t *)lv_event_get_param(e)); /*uint32_t because can be UTF-8*/ + if(c == LV_KEY_RIGHT) { + if(indev_type == LV_INDEV_TYPE_ENCODER) + lv_spinbox_increment(obj); + else + lv_spinbox_step_next(obj); + } + else if(c == LV_KEY_LEFT) { + if(indev_type == LV_INDEV_TYPE_ENCODER) + lv_spinbox_decrement(obj); + else + lv_spinbox_step_prev(obj); + } + else if(c == LV_KEY_UP) { + lv_spinbox_increment(obj); + } + else if(c == LV_KEY_DOWN) { + lv_spinbox_decrement(obj); + } + else { + lv_textarea_add_char(obj, c); + } + } +} + +static void lv_spinbox_updatevalue(lv_obj_t * obj) +{ + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + char buf[LV_SPINBOX_MAX_DIGIT_COUNT + 8]; + lv_memset_00(buf, sizeof(buf)); + char * buf_p = buf; + uint8_t cur_shift_left = 0; + + if(spinbox->range_min < 0) { // hide sign if there are only positive values + /*Add the sign*/ + (*buf_p) = spinbox->value >= 0 ? '+' : '-'; + buf_p++; + } + else { + /*Cursor need shift to left*/ + cur_shift_left++; + } + + int32_t i; + char digits[LV_SPINBOX_MAX_DIGIT_COUNT + 4]; + /*Convert the numbers to string (the sign is already handled so always covert positive number)*/ + lv_snprintf(digits, sizeof(digits), "%" LV_PRId32, LV_ABS(spinbox->value)); + + /*Add leading zeros*/ + int lz_cnt = spinbox->digit_count - (int)strlen(digits); + if(lz_cnt > 0) { + for(i = (uint16_t)strlen(digits); i >= 0; i--) { + digits[i + lz_cnt] = digits[i]; + } + for(i = 0; i < lz_cnt; i++) { + digits[i] = '0'; + } + } + + int32_t intDigits; + intDigits = (spinbox->dec_point_pos == 0) ? spinbox->digit_count : spinbox->dec_point_pos; + + /*Add the decimal part*/ + for(i = 0; i < intDigits && digits[i] != '\0'; i++) { + (*buf_p) = digits[i]; + buf_p++; + } + + if(spinbox->dec_point_pos != 0) { + /*Insert the decimal point*/ + (*buf_p) = '.'; + buf_p++; + + for(/*Leave i*/; i < spinbox->digit_count && digits[i] != '\0'; i++) { + (*buf_p) = digits[i]; + buf_p++; + } + } + + /*Refresh the text*/ + lv_textarea_set_text(obj, (char *)buf); + + /*Set the cursor position*/ + int32_t step = spinbox->step; + uint8_t cur_pos = (uint8_t)spinbox->digit_count; + while(step >= 10) { + step /= 10; + cur_pos--; + } + + if(cur_pos > intDigits) cur_pos++; /*Skip the decimal point*/ + + cur_pos -= cur_shift_left; + + lv_textarea_set_cursor_pos(obj, cur_pos); +} + +#endif /*LV_USE_SPINBOX*/ diff --git a/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.h b/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.h new file mode 100644 index 00000000..1a4bc322 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.h @@ -0,0 +1,182 @@ +/** + * @file lv_spinbox.h + * + */ + +#ifndef LV_SPINBOX_H +#define LV_SPINBOX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_SPINBOX + +/*Testing of dependencies*/ +#if LV_USE_TEXTAREA == 0 +#error "lv_spinbox: lv_ta is required. Enable it in lv_conf.h (LV_USE_TEXTAREA 1) " +#endif + +/********************* + * DEFINES + *********************/ +#define LV_SPINBOX_MAX_DIGIT_COUNT 10 + +/********************** + * TYPEDEFS + **********************/ + +/*Data of spinbox*/ +typedef struct { + lv_textarea_t ta; /*Ext. of ancestor*/ + /*New data for this type*/ + int32_t value; + int32_t range_max; + int32_t range_min; + int32_t step; + uint16_t digit_count : 4; + uint16_t dec_point_pos : 4; /*if 0, there is no separator and the number is an integer*/ + uint16_t rollover : 1; // Set to true for rollover functionality + uint16_t digit_step_dir : 2; // the direction the digit will step on encoder button press when editing +} lv_spinbox_t; + +extern const lv_obj_class_t lv_spinbox_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a Spinbox object + * @param parent pointer to an object, it will be the parent of the new spinbox + * @return pointer to the created spinbox + */ +lv_obj_t * lv_spinbox_create(lv_obj_t * parent); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set spinbox value + * @param obj pointer to spinbox + * @param i value to be set + */ +void lv_spinbox_set_value(lv_obj_t * obj, int32_t i); + +/** + * Set spinbox rollover function + * @param obj pointer to spinbox + * @param b true or false to enable or disable (default) + */ +void lv_spinbox_set_rollover(lv_obj_t * obj, bool b); + +/** + * Set spinbox digit format (digit count and decimal format) + * @param obj pointer to spinbox + * @param digit_count number of digit excluding the decimal separator and the sign + * @param separator_position number of digit before the decimal point. If 0, decimal point is not + * shown + */ +void lv_spinbox_set_digit_format(lv_obj_t * obj, uint8_t digit_count, uint8_t separator_position); + +/** + * Set spinbox step + * @param obj pointer to spinbox + * @param step steps on increment/decrement. Can be 1, 10, 100, 1000, etc the digit that will change. + */ +void lv_spinbox_set_step(lv_obj_t * obj, uint32_t step); + +/** + * Set spinbox value range + * @param obj pointer to spinbox + * @param range_min maximum value, inclusive + * @param range_max minimum value, inclusive + */ +void lv_spinbox_set_range(lv_obj_t * obj, int32_t range_min, int32_t range_max); + +/** + * Set cursor position to a specific digit for edition + * @param obj pointer to spinbox + * @param pos selected position in spinbox + */ +void lv_spinbox_set_cursor_pos(lv_obj_t * obj, uint8_t pos); + +/** + * Set direction of digit step when clicking an encoder button while in editing mode + * @param obj pointer to spinbox + * @param direction the direction (LV_DIR_RIGHT or LV_DIR_LEFT) + */ +void lv_spinbox_set_digit_step_direction(lv_obj_t * obj, lv_dir_t direction); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get spinbox rollover function status + * @param obj pointer to spinbox + */ +bool lv_spinbox_get_rollover(lv_obj_t * obj); + +/** + * Get the spinbox numeral value (user has to convert to float according to its digit format) + * @param obj pointer to spinbox + * @return value integer value of the spinbox + */ +int32_t lv_spinbox_get_value(lv_obj_t * obj); + +/** + * Get the spinbox step value (user has to convert to float according to its digit format) + * @param obj pointer to spinbox + * @return value integer step value of the spinbox + */ +int32_t lv_spinbox_get_step(lv_obj_t * obj); + +/*===================== + * Other functions + *====================*/ + +/** + * Select next lower digit for edition by dividing the step by 10 + * @param obj pointer to spinbox + */ +void lv_spinbox_step_next(lv_obj_t * obj); + +/** + * Select next higher digit for edition by multiplying the step by 10 + * @param obj pointer to spinbox + */ +void lv_spinbox_step_prev(lv_obj_t * obj); + +/** + * Increment spinbox value by one step + * @param obj pointer to spinbox + */ +void lv_spinbox_increment(lv_obj_t * obj); + +/** + * Decrement spinbox value by one step + * @param obj pointer to spinbox + */ +void lv_spinbox_decrement(lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +/* It was ambiguous in MicroPython. See https://github.com/lvgl/lvgl/issues/3301 + * TODO remove in v9*/ +#define lv_spinbox_set_pos lv_spinbox_set_cursor_pos + +#endif /*LV_USE_SPINBOX*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif +#endif /*LV_SPINBOX_H*/ diff --git a/lib/lvgl/src/extra/widgets/spinner/lv_spinner.c b/lib/lvgl/src/extra/widgets/spinner/lv_spinner.c new file mode 100644 index 00000000..6fc6d742 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/spinner/lv_spinner.c @@ -0,0 +1,104 @@ +/** + * @file lv_spinner.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_spinner.h" +#if LV_USE_SPINNER + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_spinner_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void arc_anim_start_angle(void * obj, int32_t v); +static void arc_anim_end_angle(void * obj, int32_t v); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_spinner_class = { + .base_class = &lv_arc_class, + .constructor_cb = lv_spinner_constructor +}; + +static uint32_t time_param; +static uint32_t arc_length_param; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create a spinner object + * @param parent pointer to an object, it will be the parent of the new spinner + * @return pointer to the created spinner + */ +lv_obj_t * lv_spinner_create(lv_obj_t * parent, uint32_t time, uint32_t arc_length) +{ + time_param = time; + arc_length_param = arc_length; + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_spinner_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_spinner_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + LV_UNUSED(class_p); + + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE); + + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, obj); + lv_anim_set_exec_cb(&a, arc_anim_end_angle); + lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); + lv_anim_set_time(&a, time_param); + lv_anim_set_values(&a, arc_length_param, 360 + arc_length_param); + lv_anim_start(&a); + + lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out); + lv_anim_set_values(&a, 0, 360); + lv_anim_set_exec_cb(&a, arc_anim_start_angle); + lv_anim_start(&a); + + lv_arc_set_bg_angles(obj, 0, 360); + lv_arc_set_rotation(obj, 270); +} + + +static void arc_anim_start_angle(void * obj, int32_t v) +{ + lv_arc_set_start_angle(obj, (uint16_t) v); +} + + +static void arc_anim_end_angle(void * obj, int32_t v) +{ + lv_arc_set_end_angle(obj, (uint16_t) v); +} + +#endif /*LV_USE_SPINNER*/ diff --git a/lib/lvgl/src/extra/widgets/spinner/lv_spinner.h b/lib/lvgl/src/extra/widgets/spinner/lv_spinner.h new file mode 100644 index 00000000..2ab36f64 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/spinner/lv_spinner.h @@ -0,0 +1,50 @@ +/** + * @file lv_spinner.h + * + */ + +#ifndef LV_SPINNER_H +#define LV_SPINNER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_SPINNER + +/*Testing of dependencies*/ +#if LV_USE_ARC == 0 +#error "lv_spinner: lv_arc is required. Enable it in lv_conf.h (LV_USE_ARC 1) " +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +extern const lv_obj_class_t lv_spinner_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_spinner_create(lv_obj_t * parent, uint32_t time, uint32_t arc_length); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_SPINNER*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_SPINNER_H*/ diff --git a/lib/lvgl/src/extra/widgets/tabview/lv_tabview.c b/lib/lvgl/src/extra/widgets/tabview/lv_tabview.c new file mode 100755 index 00000000..81addc66 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/tabview/lv_tabview.c @@ -0,0 +1,352 @@ +/** + * @file lv_tabview.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_tabview.h" +#if LV_USE_TABVIEW + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_tabview_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_tabview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_tabview_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_tabview_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void btns_value_changed_event_cb(lv_event_t * e); +static void cont_scroll_end_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_tabview_class = { + .constructor_cb = lv_tabview_constructor, + .destructor_cb = lv_tabview_destructor, + .event_cb = lv_tabview_event, + .width_def = LV_PCT(100), + .height_def = LV_PCT(100), + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_tabview_t) +}; + +static lv_dir_t tabpos_create; +static lv_coord_t tabsize_create; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_tabview_create(lv_obj_t * parent, lv_dir_t tab_pos, lv_coord_t tab_size) +{ + LV_LOG_INFO("begin"); + tabpos_create = tab_pos; + tabsize_create = tab_size; + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_tabview_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_tabview_add_tab(lv_obj_t * obj, const char * name) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + lv_obj_t * cont = lv_tabview_get_content(obj); + + lv_obj_t * page = lv_obj_create(cont); + lv_obj_set_size(page, LV_PCT(100), LV_PCT(100)); + lv_obj_clear_flag(page, LV_OBJ_FLAG_CLICK_FOCUSABLE); + uint32_t tab_id = lv_obj_get_child_cnt(cont); + + lv_obj_t * btns = lv_tabview_get_tab_btns(obj); + + char ** old_map = tabview->map; + char ** new_map; + + /*top or bottom dir*/ + if(tabview->tab_pos & LV_DIR_VER) { + new_map = lv_mem_alloc((tab_id + 1) * sizeof(const char *)); + lv_memcpy_small(new_map, old_map, sizeof(const char *) * (tab_id - 1)); + new_map[tab_id - 1] = lv_mem_alloc(strlen(name) + 1); + strcpy((char *)new_map[tab_id - 1], name); + new_map[tab_id] = ""; + } + /*left or right dir*/ + else { + new_map = lv_mem_alloc((tab_id * 2) * sizeof(const char *)); + lv_memcpy_small(new_map, old_map, sizeof(const char *) * (tab_id - 1) * 2); + if(tabview->tab_cnt == 0) { + new_map[0] = lv_mem_alloc(strlen(name) + 1); + strcpy((char *)new_map[0], name); + new_map[1] = ""; + } + else { + new_map[tab_id * 2 - 3] = "\n"; + new_map[tab_id * 2 - 2] = lv_mem_alloc(strlen(name) + 1); + new_map[tab_id * 2 - 1] = ""; + strcpy((char *)new_map[(tab_id * 2) - 2], name); + } + } + tabview->map = new_map; + lv_btnmatrix_set_map(btns, (const char **)new_map); + lv_mem_free(old_map); + + lv_btnmatrix_set_btn_ctrl_all(btns, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_CLICK_TRIG | + LV_BTNMATRIX_CTRL_NO_REPEAT); + + tabview->tab_cnt++; + if(tabview->tab_cnt == 1) { + lv_tabview_set_act(obj, 0, LV_ANIM_OFF); + } + + lv_btnmatrix_set_btn_ctrl(btns, tabview->tab_cur, LV_BTNMATRIX_CTRL_CHECKED); + + return page; +} + +void lv_tabview_rename_tab(lv_obj_t * obj, uint32_t id, const char * new_name) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + + if(id >= tabview->tab_cnt) return; + if(tabview->tab_pos & LV_DIR_HOR) id *= 2; + + lv_mem_free(tabview->map[id]); + tabview->map[id] = lv_mem_alloc(strlen(new_name) + 1); + strcpy(tabview->map[id], new_name); + lv_obj_invalidate(obj); +} + +void lv_tabview_set_act(lv_obj_t * obj, uint32_t id, lv_anim_enable_t anim_en) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + + if(id >= tabview->tab_cnt) { + id = tabview->tab_cnt - 1; + } + + /*To be sure lv_obj_get_content_width will return valid value*/ + lv_obj_update_layout(obj); + + lv_obj_t * cont = lv_tabview_get_content(obj); + if(cont == NULL) return; + + if((tabview->tab_pos & LV_DIR_VER) != 0) { + lv_coord_t gap = lv_obj_get_style_pad_column(cont, LV_PART_MAIN); + lv_coord_t w = lv_obj_get_content_width(cont); + if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) != LV_BASE_DIR_RTL) { + lv_obj_scroll_to_x(cont, id * (gap + w), anim_en); + } + else { + int32_t id_rtl = -(int32_t)id; + lv_obj_scroll_to_x(cont, (gap + w) * id_rtl, anim_en); + } + } + else { + lv_coord_t gap = lv_obj_get_style_pad_row(cont, LV_PART_MAIN); + lv_coord_t h = lv_obj_get_content_height(cont); + lv_obj_scroll_to_y(cont, id * (gap + h), anim_en); + } + + lv_obj_t * btns = lv_tabview_get_tab_btns(obj); + lv_btnmatrix_set_btn_ctrl(btns, id, LV_BTNMATRIX_CTRL_CHECKED); + tabview->tab_cur = id; +} + +uint16_t lv_tabview_get_tab_act(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + return tabview->tab_cur; +} + +lv_obj_t * lv_tabview_get_content(lv_obj_t * tv) +{ + return lv_obj_get_child(tv, 1); +} + +lv_obj_t * lv_tabview_get_tab_btns(lv_obj_t * tv) +{ + return lv_obj_get_child(tv, 0); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_tabview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + + tabview->tab_pos = tabpos_create; + + switch(tabview->tab_pos) { + case LV_DIR_TOP: + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + break; + case LV_DIR_BOTTOM: + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN_REVERSE); + break; + case LV_DIR_LEFT: + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + break; + case LV_DIR_RIGHT: + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW_REVERSE); + break; + } + + lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100)); + + lv_obj_t * btnm; + lv_obj_t * cont; + + btnm = lv_btnmatrix_create(obj); + cont = lv_obj_create(obj); + + lv_btnmatrix_set_one_checked(btnm, true); + tabview->map = lv_mem_alloc(sizeof(const char *)); + tabview->map[0] = ""; + lv_btnmatrix_set_map(btnm, (const char **)tabview->map); + lv_obj_add_event_cb(btnm, btns_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_add_flag(btnm, LV_OBJ_FLAG_EVENT_BUBBLE); + + lv_obj_add_event_cb(cont, cont_scroll_end_event_cb, LV_EVENT_ALL, NULL); + lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_OFF); + + switch(tabview->tab_pos) { + case LV_DIR_TOP: + case LV_DIR_BOTTOM: + lv_obj_set_size(btnm, LV_PCT(100), tabsize_create); + lv_obj_set_width(cont, LV_PCT(100)); + lv_obj_set_flex_grow(cont, 1); + break; + case LV_DIR_LEFT: + case LV_DIR_RIGHT: + lv_obj_set_size(btnm, tabsize_create, LV_PCT(100)); + lv_obj_set_height(cont, LV_PCT(100)); + lv_obj_set_flex_grow(cont, 1); + break; + } + + lv_group_t * g = lv_group_get_default(); + if(g) lv_group_add_obj(g, btnm); + + if((tabview->tab_pos & LV_DIR_VER) != 0) { + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW); + lv_obj_set_scroll_snap_x(cont, LV_SCROLL_SNAP_CENTER); + } + else { + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + lv_obj_set_scroll_snap_y(cont, LV_SCROLL_SNAP_CENTER); + } + lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ONE); + lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLL_ON_FOCUS); +} + +static void lv_tabview_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + + uint32_t i; + if(tabview->tab_pos & LV_DIR_VER) { + for(i = 0; i < tabview->tab_cnt; i++) { + lv_mem_free(tabview->map[i]); + tabview->map[i] = NULL; + } + } + if(tabview->tab_pos & LV_DIR_HOR) { + for(i = 0; i < tabview->tab_cnt; i++) { + lv_mem_free(tabview->map[i * 2]); + tabview->map[i * 2] = NULL; + } + } + + + lv_mem_free(tabview->map); + tabview->map = NULL; +} + +static void lv_tabview_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + lv_res_t res = lv_obj_event_base(&lv_tabview_class, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * target = lv_event_get_target(e); + + if(code == LV_EVENT_SIZE_CHANGED) { + lv_tabview_set_act(target, lv_tabview_get_tab_act(target), LV_ANIM_OFF); + } +} + + +static void btns_value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * btns = lv_event_get_target(e); + + lv_obj_t * tv = lv_obj_get_parent(btns); + uint32_t id = lv_btnmatrix_get_selected_btn(btns); + lv_tabview_set_act(tv, id, LV_ANIM_ON); +} + +static void cont_scroll_end_event_cb(lv_event_t * e) +{ + lv_obj_t * cont = lv_event_get_target(e); + lv_event_code_t code = lv_event_get_code(e); + + lv_obj_t * tv = lv_obj_get_parent(cont); + lv_tabview_t * tv_obj = (lv_tabview_t *)tv; + if(code == LV_EVENT_LAYOUT_CHANGED) { + lv_tabview_set_act(tv, lv_tabview_get_tab_act(tv), LV_ANIM_OFF); + } + else if(code == LV_EVENT_SCROLL_END) { + lv_indev_t * indev = lv_indev_get_act(); + if(indev && indev->proc.state == LV_INDEV_STATE_PRESSED) { + return; + } + + lv_point_t p; + lv_obj_get_scroll_end(cont, &p); + + lv_coord_t t; + if((tv_obj->tab_pos & LV_DIR_VER) != 0) { + lv_coord_t w = lv_obj_get_content_width(cont); + if(lv_obj_get_style_base_dir(tv, LV_PART_MAIN) == LV_BASE_DIR_RTL) t = -(p.x - w / 2) / w; + else t = (p.x + w / 2) / w; + } + else { + lv_coord_t h = lv_obj_get_content_height(cont); + t = (p.y + h / 2) / h; + } + + if(t < 0) t = 0; + bool new_tab = false; + if(t != lv_tabview_get_tab_act(tv)) new_tab = true; + lv_tabview_set_act(tv, t, LV_ANIM_ON); + + if(new_tab) lv_event_send(tv, LV_EVENT_VALUE_CHANGED, NULL); + } +} +#endif /*LV_USE_TABVIEW*/ diff --git a/lib/lvgl/src/extra/widgets/tabview/lv_tabview.h b/lib/lvgl/src/extra/widgets/tabview/lv_tabview.h new file mode 100644 index 00000000..388c6547 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/tabview/lv_tabview.h @@ -0,0 +1,65 @@ +/** + * @file lv_templ.h + * + */ + +#ifndef LV_TABVIEW_H +#define LV_TABVIEW_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_TABVIEW + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_obj_t obj; + char ** map; + uint16_t tab_cnt; + uint16_t tab_cur; + lv_dir_t tab_pos; +} lv_tabview_t; + +extern const lv_obj_class_t lv_tabview_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ +lv_obj_t * lv_tabview_create(lv_obj_t * parent, lv_dir_t tab_pos, lv_coord_t tab_size); + +lv_obj_t * lv_tabview_add_tab(lv_obj_t * tv, const char * name); + +void lv_tabview_rename_tab(lv_obj_t * obj, uint32_t tab_id, const char * new_name); + +lv_obj_t * lv_tabview_get_content(lv_obj_t * tv); + +lv_obj_t * lv_tabview_get_tab_btns(lv_obj_t * tv); + +void lv_tabview_set_act(lv_obj_t * obj, uint32_t id, lv_anim_enable_t anim_en); + +uint16_t lv_tabview_get_tab_act(lv_obj_t * tv); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_TABVIEW*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_TABVIEW_H*/ diff --git a/lib/lvgl/src/extra/widgets/tileview/lv_tileview.c b/lib/lvgl/src/extra/widgets/tileview/lv_tileview.c new file mode 100644 index 00000000..17fdb519 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/tileview/lv_tileview.c @@ -0,0 +1,194 @@ +/** + * @file lv_tileview.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_tileview.h" +#include "../../../core/lv_indev.h" +#if LV_USE_TILEVIEW + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_tileview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_tileview_tile_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void tileview_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ + +const lv_obj_class_t lv_tileview_class = {.constructor_cb = lv_tileview_constructor, + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_tileview_t) + }; + +const lv_obj_class_t lv_tileview_tile_class = {.constructor_cb = lv_tileview_tile_constructor, + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_tileview_tile_t) + }; + +static lv_dir_t create_dir; +static uint32_t create_col_id; +static uint32_t create_row_id; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_tileview_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_tileview_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*====================== + * Add/remove functions + *=====================*/ + +lv_obj_t * lv_tileview_add_tile(lv_obj_t * tv, uint8_t col_id, uint8_t row_id, lv_dir_t dir) +{ + LV_LOG_INFO("begin"); + create_dir = dir; + create_col_id = col_id; + create_row_id = row_id; + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_tileview_tile_class, tv); + lv_obj_class_init_obj(obj); + return obj; +} + +void lv_obj_set_tile(lv_obj_t * obj, lv_obj_t * tile_obj, lv_anim_enable_t anim_en) +{ + lv_coord_t tx = lv_obj_get_x(tile_obj); + lv_coord_t ty = lv_obj_get_y(tile_obj); + + lv_tileview_tile_t * tile = (lv_tileview_tile_t *)tile_obj; + lv_tileview_t * tv = (lv_tileview_t *) obj; + tv->tile_act = (lv_obj_t *)tile; + + lv_obj_set_scroll_dir(obj, tile->dir); + lv_obj_scroll_to(obj, tx, ty, anim_en); +} + +void lv_obj_set_tile_id(lv_obj_t * tv, uint32_t col_id, uint32_t row_id, lv_anim_enable_t anim_en) +{ + lv_obj_update_layout(tv); + + lv_coord_t w = lv_obj_get_content_width(tv); + lv_coord_t h = lv_obj_get_content_height(tv); + + lv_coord_t tx = col_id * w; + lv_coord_t ty = row_id * h; + + uint32_t i; + for(i = 0; i < lv_obj_get_child_cnt(tv); i++) { + lv_obj_t * tile_obj = lv_obj_get_child(tv, i); + lv_coord_t x = lv_obj_get_x(tile_obj); + lv_coord_t y = lv_obj_get_y(tile_obj); + if(x == tx && y == ty) { + lv_obj_set_tile(tv, tile_obj, anim_en); + return; + } + } + + LV_LOG_WARN("No tile found with at (%d,%d) index", (int)col_id, (int)row_id); +} + +lv_obj_t * lv_tileview_get_tile_act(lv_obj_t * obj) +{ + lv_tileview_t * tv = (lv_tileview_t *) obj; + return tv->tile_act; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_tileview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100)); + lv_obj_add_event_cb(obj, tileview_event_cb, LV_EVENT_ALL, NULL); + lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ONE); + lv_obj_set_scroll_snap_x(obj, LV_SCROLL_SNAP_CENTER); + lv_obj_set_scroll_snap_y(obj, LV_SCROLL_SNAP_CENTER); + +} + +static void lv_tileview_tile_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + + LV_UNUSED(class_p); + lv_obj_t * parent = lv_obj_get_parent(obj); + lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100)); + lv_obj_update_layout(obj); /*Be sure the size is correct*/ + lv_obj_set_pos(obj, create_col_id * lv_obj_get_content_width(parent), + create_row_id * lv_obj_get_content_height(parent)); + + lv_tileview_tile_t * tile = (lv_tileview_tile_t *)obj; + tile->dir = create_dir; + + if(create_col_id == 0 && create_row_id == 0) { + lv_obj_set_scroll_dir(parent, create_dir); + } +} + +static void tileview_event_cb(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + lv_tileview_t * tv = (lv_tileview_t *) obj; + + if(code == LV_EVENT_SCROLL_END) { + lv_indev_t * indev = lv_indev_get_act(); + if(indev && indev->proc.state == LV_INDEV_STATE_PRESSED) { + return; + } + + lv_coord_t w = lv_obj_get_content_width(obj); + lv_coord_t h = lv_obj_get_content_height(obj); + + lv_point_t scroll_end; + lv_obj_get_scroll_end(obj, &scroll_end); + lv_coord_t left = scroll_end.x; + lv_coord_t top = scroll_end.y; + + lv_coord_t tx = ((left + (w / 2)) / w) * w; + lv_coord_t ty = ((top + (h / 2)) / h) * h; + + lv_dir_t dir = LV_DIR_ALL; + uint32_t i; + for(i = 0; i < lv_obj_get_child_cnt(obj); i++) { + lv_obj_t * tile_obj = lv_obj_get_child(obj, i); + lv_coord_t x = lv_obj_get_x(tile_obj); + lv_coord_t y = lv_obj_get_y(tile_obj); + if(x == tx && y == ty) { + lv_tileview_tile_t * tile = (lv_tileview_tile_t *)tile_obj; + tv->tile_act = (lv_obj_t *)tile; + dir = tile->dir; + lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + break; + } + } + lv_obj_set_scroll_dir(obj, dir); + } +} +#endif /*LV_USE_TILEVIEW*/ diff --git a/lib/lvgl/src/extra/widgets/tileview/lv_tileview.h b/lib/lvgl/src/extra/widgets/tileview/lv_tileview.h new file mode 100644 index 00000000..7adeec33 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/tileview/lv_tileview.h @@ -0,0 +1,72 @@ +/** + * @file lv_tileview.h + * + */ + +#ifndef LV_TILEVIEW_H +#define LV_TILEVIEW_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" + +#if LV_USE_TILEVIEW + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_obj_t obj; + lv_obj_t * tile_act; +} lv_tileview_t; + +typedef struct { + lv_obj_t obj; + lv_dir_t dir; +} lv_tileview_tile_t; + +extern const lv_obj_class_t lv_tileview_class; +extern const lv_obj_class_t lv_tileview_tile_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a Tileview object + * @param parent pointer to an object, it will be the parent of the new tileview + * @return pointer to the created tileview + */ +lv_obj_t * lv_tileview_create(lv_obj_t * parent); + +lv_obj_t * lv_tileview_add_tile(lv_obj_t * tv, uint8_t col_id, uint8_t row_id, lv_dir_t dir); + +void lv_obj_set_tile(lv_obj_t * tv, lv_obj_t * tile_obj, lv_anim_enable_t anim_en); +void lv_obj_set_tile_id(lv_obj_t * tv, uint32_t col_id, uint32_t row_id, lv_anim_enable_t anim_en); + +lv_obj_t * lv_tileview_get_tile_act(lv_obj_t * obj); + +/*===================== + * Other functions + *====================*/ + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_TILEVIEW*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_TILEVIEW_H*/ diff --git a/lib/lvgl/src/extra/widgets/win/lv_win.c b/lib/lvgl/src/extra/widgets/win/lv_win.c new file mode 100644 index 00000000..92c3b8ba --- /dev/null +++ b/lib/lvgl/src/extra/widgets/win/lv_win.c @@ -0,0 +1,110 @@ +/** + * @file lv_win.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_win.h" +#if LV_USE_WIN + + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_win_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_win_class = { + .constructor_cb = lv_win_constructor, + .width_def = LV_PCT(100), + .height_def = LV_PCT(100), + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_win_t) +}; +static lv_coord_t create_header_height; +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_win_create(lv_obj_t * parent, lv_coord_t header_height) +{ + LV_LOG_INFO("begin"); + create_header_height = header_height; + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_win_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_win_add_title(lv_obj_t * win, const char * txt) +{ + lv_obj_t * header = lv_win_get_header(win); + lv_obj_t * title = lv_label_create(header); + lv_label_set_long_mode(title, LV_LABEL_LONG_DOT); + lv_label_set_text(title, txt); + lv_obj_set_flex_grow(title, 1); + return title; +} + +lv_obj_t * lv_win_add_btn(lv_obj_t * win, const void * icon, lv_coord_t btn_w) +{ + lv_obj_t * header = lv_win_get_header(win); + lv_obj_t * btn = lv_btn_create(header); + lv_obj_set_size(btn, btn_w, LV_PCT(100)); + + lv_obj_t * img = lv_img_create(btn); + lv_img_set_src(img, icon); + lv_obj_align(img, LV_ALIGN_CENTER, 0, 0); + + return btn; +} + +lv_obj_t * lv_win_get_header(lv_obj_t * win) +{ + return lv_obj_get_child(win, 0); +} + +lv_obj_t * lv_win_get_content(lv_obj_t * win) +{ + return lv_obj_get_child(win, 1); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_win_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_obj_t * parent = lv_obj_get_parent(obj); + lv_obj_set_size(obj, lv_obj_get_width(parent), lv_obj_get_height(parent)); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + + lv_obj_t * header = lv_obj_create(obj); + lv_obj_set_size(header, LV_PCT(100), create_header_height); + lv_obj_set_flex_flow(header, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + lv_obj_t * cont = lv_obj_create(obj); + lv_obj_set_flex_grow(cont, 1); + lv_obj_set_width(cont, LV_PCT(100)); +} + +#endif + diff --git a/lib/lvgl/src/extra/widgets/win/lv_win.h b/lib/lvgl/src/extra/widgets/win/lv_win.h new file mode 100644 index 00000000..4342b310 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/win/lv_win.h @@ -0,0 +1,51 @@ +/** + * @file lv_win.h + * + */ + +#ifndef LV_WIN_H +#define LV_WIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_obj_t obj; +} lv_win_t; + +extern const lv_obj_class_t lv_win_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_win_create(lv_obj_t * parent, lv_coord_t header_height); + + +lv_obj_t * lv_win_add_title(lv_obj_t * win, const char * txt); +lv_obj_t * lv_win_add_btn(lv_obj_t * win, const void * icon, lv_coord_t btn_w); + +lv_obj_t * lv_win_get_header(lv_obj_t * win); +lv_obj_t * lv_win_get_content(lv_obj_t * win); +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_WIN_H*/ -- cgit v1.2.3