summaryrefslogtreecommitdiff
path: root/lib/lvgl/src/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'lib/lvgl/src/widgets')
m---------lib/lvgl0
-rw-r--r--lib/lvgl/src/widgets/lv_arc.c844
-rw-r--r--lib/lvgl/src/widgets/lv_arc.h256
-rw-r--r--lib/lvgl/src/widgets/lv_bar.c611
-rw-r--r--lib/lvgl/src/widgets/lv_bar.h164
-rw-r--r--lib/lvgl/src/widgets/lv_btn.c72
-rw-r--r--lib/lvgl/src/widgets/lv_btn.h56
-rw-r--r--lib/lvgl/src/widgets/lv_btnmatrix.c1048
-rw-r--r--lib/lvgl/src/widgets/lv_btnmatrix.h225
-rw-r--r--lib/lvgl/src/widgets/lv_canvas.c836
-rw-r--r--lib/lvgl/src/widgets/lv_canvas.h280
-rw-r--r--lib/lvgl/src/widgets/lv_checkbox.c262
-rw-r--r--lib/lvgl/src/widgets/lv_checkbox.h97
-rw-r--r--lib/lvgl/src/widgets/lv_dropdown.c1147
-rw-r--r--lib/lvgl/src/widgets/lv_dropdown.h254
-rw-r--r--lib/lvgl/src/widgets/lv_img.c693
-rw-r--r--lib/lvgl/src/widgets/lv_img.h234
-rw-r--r--lib/lvgl/src/widgets/lv_label.c1274
-rw-r--r--lib/lvgl/src/widgets/lv_label.h246
-rw-r--r--lib/lvgl/src/widgets/lv_line.c201
-rw-r--r--lib/lvgl/src/widgets/lv_line.h93
-rw-r--r--lib/lvgl/src/widgets/lv_objx_templ.c140
-rw-r--r--lib/lvgl/src/widgets/lv_objx_templ.h81
-rw-r--r--lib/lvgl/src/widgets/lv_roller.c786
-rw-r--r--lib/lvgl/src/widgets/lv_roller.h138
-rw-r--r--lib/lvgl/src/widgets/lv_slider.c443
-rw-r--r--lib/lvgl/src/widgets/lv_slider.h195
-rw-r--r--lib/lvgl/src/widgets/lv_switch.c277
-rw-r--r--lib/lvgl/src/widgets/lv_switch.h61
-rw-r--r--lib/lvgl/src/widgets/lv_table.c1007
-rw-r--r--lib/lvgl/src/widgets/lv_table.h210
-rw-r--r--lib/lvgl/src/widgets/lv_textarea.c1370
-rw-r--r--lib/lvgl/src/widgets/lv_textarea.h358
-rw-r--r--lib/lvgl/src/widgets/lv_widgets.mk20
34 files changed, 13979 insertions, 0 deletions
diff --git a/lib/lvgl b/lib/lvgl
deleted file mode 160000
-Subproject 0732400e7b564dd0e7dc4a924619d8e19c5b23a
diff --git a/lib/lvgl/src/widgets/lv_arc.c b/lib/lvgl/src/widgets/lv_arc.c
new file mode 100644
index 00000000..6cab5f33
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_arc.c
@@ -0,0 +1,844 @@
+/**
+ * @file lv_arc.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_arc.h"
+#if LV_USE_ARC != 0
+
+#include "../core/lv_group.h"
+#include "../core/lv_indev.h"
+#include "../misc/lv_assert.h"
+#include "../misc/lv_math.h"
+#include "../draw/lv_draw_arc.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_arc_class
+
+#define VALUE_UNSET INT16_MIN
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+static void lv_arc_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_arc_draw(lv_event_t * e);
+static void lv_arc_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void inv_arc_area(lv_obj_t * arc, uint16_t start_angle, uint16_t end_angle, lv_part_t part);
+static void inv_knob_area(lv_obj_t * obj);
+static void get_center(const lv_obj_t * obj, lv_point_t * center, lv_coord_t * arc_r);
+static lv_coord_t get_angle(const lv_obj_t * obj);
+static void get_knob_area(lv_obj_t * arc, const lv_point_t * center, lv_coord_t r, lv_area_t * knob_area);
+static void value_update(lv_obj_t * arc);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_arc_class = {
+ .constructor_cb = lv_arc_constructor,
+ .event_cb = lv_arc_event,
+ .instance_size = sizeof(lv_arc_t),
+ .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_arc_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/remove functions
+ *=====================*/
+
+/*
+ * New object specific "add" or "remove" functions come here
+ */
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+void lv_arc_set_start_angle(lv_obj_t * obj, uint16_t start)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ if(start > 360) start -= 360;
+
+ int16_t old_delta = arc->indic_angle_end - arc->indic_angle_start;
+ int16_t new_delta = arc->indic_angle_end - start;
+
+ if(old_delta < 0) old_delta = 360 + old_delta;
+ if(new_delta < 0) new_delta = 360 + new_delta;
+
+ if(LV_ABS(new_delta - old_delta) > 180) lv_obj_invalidate(obj);
+ else if(new_delta < old_delta) inv_arc_area(obj, arc->indic_angle_start, start, LV_PART_INDICATOR);
+ else if(old_delta < new_delta) inv_arc_area(obj, start, arc->indic_angle_start, LV_PART_INDICATOR);
+
+ inv_knob_area(obj);
+
+ arc->indic_angle_start = start;
+
+ inv_knob_area(obj);
+}
+
+void lv_arc_set_end_angle(lv_obj_t * obj, uint16_t end)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+ if(end > 360) end -= 360;
+
+ int16_t old_delta = arc->indic_angle_end - arc->indic_angle_start;
+ int16_t new_delta = end - arc->indic_angle_start;
+
+ if(old_delta < 0) old_delta = 360 + old_delta;
+ if(new_delta < 0) new_delta = 360 + new_delta;
+
+ if(LV_ABS(new_delta - old_delta) > 180) lv_obj_invalidate(obj);
+ else if(new_delta < old_delta) inv_arc_area(obj, end, arc->indic_angle_end, LV_PART_INDICATOR);
+ else if(old_delta < new_delta) inv_arc_area(obj, arc->indic_angle_end, end, LV_PART_INDICATOR);
+
+ inv_knob_area(obj);
+
+ arc->indic_angle_end = end;
+
+ inv_knob_area(obj);
+}
+
+void lv_arc_set_angles(lv_obj_t * obj, uint16_t start, uint16_t end)
+{
+ lv_arc_set_end_angle(obj, end);
+ lv_arc_set_start_angle(obj, start);
+}
+
+void lv_arc_set_bg_start_angle(lv_obj_t * obj, uint16_t start)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ if(start > 360) start -= 360;
+
+ int16_t old_delta = arc->bg_angle_end - arc->bg_angle_start;
+ int16_t new_delta = arc->bg_angle_end - start;
+
+ if(old_delta < 0) old_delta = 360 + old_delta;
+ if(new_delta < 0) new_delta = 360 + new_delta;
+
+ if(LV_ABS(new_delta - old_delta) > 180) lv_obj_invalidate(obj);
+ else if(new_delta < old_delta) inv_arc_area(obj, arc->bg_angle_start, start, LV_PART_MAIN);
+ else if(old_delta < new_delta) inv_arc_area(obj, start, arc->bg_angle_start, LV_PART_MAIN);
+
+ arc->bg_angle_start = start;
+
+ value_update(obj);
+}
+
+void lv_arc_set_bg_end_angle(lv_obj_t * obj, uint16_t end)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ if(end > 360) end -= 360;
+
+ int16_t old_delta = arc->bg_angle_end - arc->bg_angle_start;
+ int16_t new_delta = end - arc->bg_angle_start;
+
+ if(old_delta < 0) old_delta = 360 + old_delta;
+ if(new_delta < 0) new_delta = 360 + new_delta;
+
+ if(LV_ABS(new_delta - old_delta) > 180) lv_obj_invalidate(obj);
+ else if(new_delta < old_delta) inv_arc_area(obj, end, arc->bg_angle_end, LV_PART_MAIN);
+ else if(old_delta < new_delta) inv_arc_area(obj, arc->bg_angle_end, end, LV_PART_MAIN);
+
+ arc->bg_angle_end = end;
+
+ value_update(obj);
+}
+
+void lv_arc_set_bg_angles(lv_obj_t * obj, uint16_t start, uint16_t end)
+{
+ lv_arc_set_bg_end_angle(obj, end);
+ lv_arc_set_bg_start_angle(obj, start);
+}
+
+void lv_arc_set_rotation(lv_obj_t * obj, uint16_t rotation)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ arc->rotation = rotation;
+
+ lv_obj_invalidate(obj);
+}
+
+void lv_arc_set_mode(lv_obj_t * obj, lv_arc_mode_t type)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ int16_t val = arc->value;
+
+ arc->type = type;
+ arc->value = -1; /** Force set_value handling*/
+
+ int16_t bg_midpoint, bg_end = arc->bg_angle_end;
+ if(arc->bg_angle_end < arc->bg_angle_start) bg_end = arc->bg_angle_end + 360;
+
+ switch(arc->type) {
+ case LV_ARC_MODE_SYMMETRICAL:
+ bg_midpoint = (arc->bg_angle_start + bg_end) / 2;
+ lv_arc_set_start_angle(obj, bg_midpoint);
+ lv_arc_set_end_angle(obj, bg_midpoint);
+ break;
+ case LV_ARC_MODE_REVERSE:
+ lv_arc_set_end_angle(obj, arc->bg_angle_end);
+ break;
+ default: /** LV_ARC_TYPE_NORMAL*/
+ lv_arc_set_start_angle(obj, arc->bg_angle_start);
+ }
+
+ lv_arc_set_value(obj, val);
+}
+
+void lv_arc_set_value(lv_obj_t * obj, int16_t value)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ if(arc->value == value) return;
+
+ int16_t new_value;
+ new_value = value > arc->max_value ? arc->max_value : value;
+ new_value = new_value < arc->min_value ? arc->min_value : new_value;
+
+ if(arc->value == new_value) return;
+ arc->value = new_value;
+
+ value_update(obj);
+}
+
+void lv_arc_set_range(lv_obj_t * obj, int16_t min, int16_t max)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ if(arc->min_value == min && arc->max_value == max) return;
+
+ arc->min_value = min;
+ arc->max_value = max;
+
+ if(arc->value < min) {
+ arc->value = min;
+ }
+ if(arc->value > max) {
+ arc->value = max;
+ }
+
+ value_update(obj); /*value has changed relative to the new range*/
+}
+
+void lv_arc_set_change_rate(lv_obj_t * obj, uint16_t rate)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ arc->chg_rate = rate;
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+uint16_t lv_arc_get_angle_start(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ return ((lv_arc_t *) obj)->indic_angle_start;
+}
+
+uint16_t lv_arc_get_angle_end(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ return ((lv_arc_t *) obj)->indic_angle_end;
+}
+
+uint16_t lv_arc_get_bg_angle_start(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ return ((lv_arc_t *) obj)->bg_angle_start;
+}
+
+uint16_t lv_arc_get_bg_angle_end(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ return ((lv_arc_t *) obj)->bg_angle_end;
+}
+
+int16_t lv_arc_get_value(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ return ((lv_arc_t *) obj)->value;
+}
+
+int16_t lv_arc_get_min_value(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ return ((lv_arc_t *) obj)->min_value;
+}
+
+int16_t lv_arc_get_max_value(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ return ((lv_arc_t *) obj)->max_value;
+}
+
+lv_arc_mode_t lv_arc_get_mode(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ return ((lv_arc_t *) obj)->type;
+}
+
+/*=====================
+ * Other functions
+ *====================*/
+
+
+void lv_arc_align_obj_to_angle(const lv_obj_t * obj, lv_obj_t * obj_to_align, lv_coord_t r_offset)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(obj_to_align);
+
+ lv_obj_update_layout(obj);
+
+ lv_point_t center;
+ lv_coord_t arc_r;
+ get_center(obj, &center, &arc_r);
+ lv_coord_t indic_width = lv_obj_get_style_arc_width(obj, LV_PART_INDICATOR);
+ lv_coord_t indic_width_half = indic_width / 2;
+ arc_r -= indic_width_half;
+ arc_r += r_offset;
+
+ uint16_t angle = get_angle(obj);
+ lv_coord_t knob_x = (arc_r * lv_trigo_sin(angle + 90)) >> LV_TRIGO_SHIFT;
+ lv_coord_t knob_y = (arc_r * lv_trigo_sin(angle)) >> LV_TRIGO_SHIFT;
+ lv_obj_align_to(obj_to_align, obj, LV_ALIGN_CENTER, knob_x, knob_y);
+}
+
+void lv_arc_rotate_obj_to_angle(const lv_obj_t * obj, lv_obj_t * obj_to_rotate, lv_coord_t r_offset)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(obj_to_rotate);
+
+ lv_obj_update_layout(obj);
+
+ lv_point_t center;
+ lv_coord_t arc_r;
+ get_center(obj, &center, &arc_r);
+ lv_coord_t indic_width = lv_obj_get_style_arc_width(obj, LV_PART_INDICATOR);
+ lv_coord_t indic_width_half = indic_width / 2;
+ arc_r -= indic_width_half;
+
+ arc_r += r_offset;
+ lv_obj_align_to(obj_to_rotate, obj, LV_ALIGN_CENTER, 0, -arc_r);
+
+ lv_obj_update_layout(obj);
+
+ uint16_t angle = get_angle(obj);
+ lv_coord_t pivot_x = obj_to_rotate->coords.x1 - center.x;
+ lv_coord_t pivot_y = obj_to_rotate->coords.y1 - center.y;
+ lv_obj_set_style_transform_pivot_x(obj_to_rotate, -pivot_x, 0);
+ lv_obj_set_style_transform_pivot_y(obj_to_rotate, -pivot_y, 0);
+ lv_obj_set_style_transform_angle(obj_to_rotate, angle * 10 + 900, 0);
+}
+
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_arc_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ /*Initialize the allocated 'ext'*/
+ arc->rotation = 0;
+ arc->bg_angle_start = 135;
+ arc->bg_angle_end = 45;
+ arc->indic_angle_start = 135;
+ arc->indic_angle_end = 270;
+ arc->type = LV_ARC_MODE_NORMAL;
+ arc->value = VALUE_UNSET;
+ arc->min_close = 1;
+ arc->min_value = 0;
+ arc->max_value = 100;
+ arc->dragging = false;
+ arc->chg_rate = 720;
+ arc->last_tick = lv_tick_get();
+ arc->last_angle = arc->indic_angle_end;
+
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN | LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_set_ext_click_area(obj, LV_DPI_DEF / 10);
+
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_arc_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*/
+ 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_arc_t * arc = (lv_arc_t *)lv_event_get_target(e);
+ if(code == LV_EVENT_PRESSING) {
+ lv_indev_t * indev = lv_indev_get_act();
+ if(indev == NULL) return;
+
+ /*Handle only pointers here*/
+ lv_indev_type_t indev_type = lv_indev_get_type(indev);
+ if(indev_type != LV_INDEV_TYPE_POINTER) return;
+
+ lv_point_t p;
+ lv_indev_get_point(indev, &p);
+
+ /*Make point relative to the arc's center*/
+ lv_point_t center;
+ lv_coord_t r;
+ get_center(obj, &center, &r);
+
+ p.x -= center.x;
+ p.y -= center.y;
+
+ /*Enter dragging mode if pressed out of the knob*/
+ if(arc->dragging == false) {
+ lv_coord_t indic_width = lv_obj_get_style_arc_width(obj, LV_PART_INDICATOR);
+ r -= indic_width;
+ /*Add some more sensitive area if there is no advanced git testing.
+ * (Advanced hit testing is more precise)*/
+ if(lv_obj_has_flag(obj, LV_OBJ_FLAG_ADV_HITTEST)) {
+ r -= indic_width;
+
+ }
+ else {
+ r -= LV_MAX(r / 4, indic_width);
+ }
+ if(r < 1) r = 1;
+
+ if(p.x * p.x + p.y * p.y > r * r) {
+ arc->dragging = true;
+ arc->last_tick = lv_tick_get(); /*Capture timestamp at dragging start*/
+ }
+ }
+
+ /*It must be in "dragging" mode to turn the arc*/
+ if(arc->dragging == false) return;
+
+ /*No angle can be determined if exactly the middle of the arc is being pressed*/
+ if(p.x == 0 && p.y == 0) return;
+
+ /*Calculate the angle of the pressed point*/
+ int16_t angle;
+ int16_t bg_end = arc->bg_angle_end;
+ if(arc->bg_angle_end < arc->bg_angle_start) {
+ bg_end = arc->bg_angle_end + 360;
+ }
+
+ angle = lv_atan2(p.y, p.x);
+ angle -= arc->rotation;
+ angle -= arc->bg_angle_start; /*Make the angle relative to the start angle*/
+
+ if(angle < 0) angle += 360;
+
+ int16_t deg_range = bg_end - arc->bg_angle_start;
+
+ int16_t last_angle_rel = arc->last_angle - arc->bg_angle_start;
+ int16_t delta_angle = angle - last_angle_rel;
+
+ /*Do not allow big jumps.
+ *It's mainly to avoid jumping to the opposite end if the "dead" range between min. and max. is crossed.
+ *Check which end was closer on the last valid press (arc->min_close) and prefer that end*/
+ if(LV_ABS(delta_angle) > 280) {
+ if(arc->min_close) angle = 0;
+ else angle = deg_range;
+ }
+ else {
+ if(angle < deg_range / 2)arc->min_close = 1;
+ else arc->min_close = 0;
+ }
+
+ /*Calculate the slew rate limited angle based on change rate (degrees/sec)*/
+ delta_angle = angle - last_angle_rel;
+ uint32_t delta_tick = lv_tick_elaps(arc->last_tick);
+ int16_t delta_angle_max = (arc->chg_rate * delta_tick) / 1000;
+
+ if(delta_angle > delta_angle_max) {
+ delta_angle = delta_angle_max;
+ }
+ else if(delta_angle < -delta_angle_max) {
+ delta_angle = -delta_angle_max;
+ }
+
+ angle = last_angle_rel + delta_angle; /*Apply the limited angle change*/
+
+ /*Rounding for symmetry*/
+ int32_t round = ((bg_end - arc->bg_angle_start) * 8) / (arc->max_value - arc->min_value);
+ round = (round + 4) >> 4;
+ angle += round;
+
+ angle += arc->bg_angle_start; /*Make the angle absolute again*/
+
+ /*Set the new value*/
+ int16_t old_value = arc->value;
+ int16_t new_value = lv_map(angle, arc->bg_angle_start, bg_end, arc->min_value, arc->max_value);
+ if(new_value != lv_arc_get_value(obj)) {
+ arc->last_tick = lv_tick_get(); /*Cache timestamp for the next iteration*/
+ lv_arc_set_value(obj, new_value); /*set_value caches the last_angle for the next iteration*/
+ if(new_value != old_value) {
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
+ if(res != LV_RES_OK) return;
+ }
+ }
+
+ /*Don't let the elapsed time become too big while sitting on an end point*/
+ if(new_value == arc->min_value || new_value == arc->max_value) {
+ arc->last_tick = lv_tick_get(); /*Cache timestamp for the next iteration*/
+ }
+ }
+ else if(code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
+ arc->dragging = false;
+
+ /*Leave edit mode if released. (No need to wait for LONG_PRESS)*/
+ lv_group_t * g = lv_obj_get_group(obj);
+ bool editing = lv_group_get_editing(g);
+ lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
+ if(indev_type == LV_INDEV_TYPE_ENCODER) {
+ if(editing) lv_group_set_editing(g, false);
+ }
+
+ }
+ else if(code == LV_EVENT_KEY) {
+ char c = *((char *)lv_event_get_param(e));
+
+ int16_t old_value = arc->value;
+ if(c == LV_KEY_RIGHT || c == LV_KEY_UP) {
+ lv_arc_set_value(obj, lv_arc_get_value(obj) + 1);
+ }
+ else if(c == LV_KEY_LEFT || c == LV_KEY_DOWN) {
+ lv_arc_set_value(obj, lv_arc_get_value(obj) - 1);
+ }
+
+ if(old_value != arc->value) {
+ 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);;
+
+ lv_point_t p;
+ lv_coord_t r;
+ get_center(obj, &p, &r);
+
+ lv_coord_t ext_click_area = 0;
+ if(obj->spec_attr) ext_click_area = obj->spec_attr->ext_click_pad;
+
+ lv_coord_t w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN);
+ r -= w + ext_click_area;
+
+ lv_area_t a;
+ /*Invalid if clicked inside*/
+ lv_area_set(&a, p.x - r, p.y - r, p.x + r, p.y + r);
+ if(_lv_area_is_point_on(&a, info->point, LV_RADIUS_CIRCLE)) {
+ info->res = false;
+ return;
+ }
+
+ /*Valid if no clicked outside*/
+ lv_area_increase(&a, w + ext_click_area * 2, w + ext_click_area * 2);
+ info->res = _lv_area_is_point_on(&a, info->point, LV_RADIUS_CIRCLE);
+ }
+ else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
+ lv_coord_t bg_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t bg_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+ lv_coord_t bg_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t bg_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
+ lv_coord_t bg_pad = LV_MAX4(bg_left, bg_right, bg_top, bg_bottom);
+
+ lv_coord_t knob_left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
+ lv_coord_t knob_right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
+ lv_coord_t knob_top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
+ lv_coord_t knob_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
+ lv_coord_t knob_pad = LV_MAX4(knob_left, knob_right, knob_top, knob_bottom) + 2;
+
+ lv_coord_t * s = lv_event_get_param(e);
+ *s = LV_MAX(*s, knob_pad - bg_pad);
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ lv_arc_draw(e);
+ }
+}
+
+static void lv_arc_draw(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ lv_point_t center;
+ lv_coord_t arc_r;
+ get_center(obj, &center, &arc_r);
+
+ lv_obj_draw_part_dsc_t part_draw_dsc;
+ lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
+
+ /*Draw the background arc*/
+ lv_draw_arc_dsc_t arc_dsc;
+ if(arc_r > 0) {
+ lv_draw_arc_dsc_init(&arc_dsc);
+ lv_obj_init_draw_arc_dsc(obj, LV_PART_MAIN, &arc_dsc);
+
+ part_draw_dsc.part = LV_PART_MAIN;
+ part_draw_dsc.class_p = MY_CLASS;
+ part_draw_dsc.type = LV_ARC_DRAW_PART_BACKGROUND;
+ part_draw_dsc.p1 = &center;
+ part_draw_dsc.radius = arc_r;
+ part_draw_dsc.arc_dsc = &arc_dsc;
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+
+ lv_draw_arc(draw_ctx, &arc_dsc, &center, part_draw_dsc.radius, arc->bg_angle_start + arc->rotation,
+ arc->bg_angle_end + arc->rotation);
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+ }
+
+ /*Make the indicator arc smaller or larger according to its greatest padding value*/
+ lv_coord_t left_indic = lv_obj_get_style_pad_left(obj, LV_PART_INDICATOR);
+ lv_coord_t right_indic = lv_obj_get_style_pad_right(obj, LV_PART_INDICATOR);
+ lv_coord_t top_indic = lv_obj_get_style_pad_top(obj, LV_PART_INDICATOR);
+ lv_coord_t bottom_indic = lv_obj_get_style_pad_bottom(obj, LV_PART_INDICATOR);
+ lv_coord_t indic_r = arc_r - LV_MAX4(left_indic, right_indic, top_indic, bottom_indic);
+
+ if(indic_r > 0) {
+ lv_draw_arc_dsc_init(&arc_dsc);
+ lv_obj_init_draw_arc_dsc(obj, LV_PART_INDICATOR, &arc_dsc);
+
+ part_draw_dsc.part = LV_PART_INDICATOR;
+ part_draw_dsc.class_p = MY_CLASS;
+ part_draw_dsc.type = LV_ARC_DRAW_PART_FOREGROUND;
+ part_draw_dsc.p1 = &center;
+ part_draw_dsc.radius = indic_r;
+ part_draw_dsc.arc_dsc = &arc_dsc;
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+
+ if(arc_dsc.width > part_draw_dsc.radius) arc_dsc.width = part_draw_dsc.radius;
+ lv_draw_arc(draw_ctx, &arc_dsc, &center, part_draw_dsc.radius, arc->indic_angle_start + arc->rotation,
+ arc->indic_angle_end + arc->rotation);
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+ }
+
+ lv_area_t knob_area;
+ get_knob_area(obj, &center, arc_r, &knob_area);
+
+ lv_draw_rect_dsc_t knob_rect_dsc;
+ lv_draw_rect_dsc_init(&knob_rect_dsc);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_KNOB, &knob_rect_dsc);
+
+ part_draw_dsc.part = LV_PART_KNOB;
+ part_draw_dsc.class_p = MY_CLASS;
+ part_draw_dsc.type = LV_ARC_DRAW_PART_KNOB;
+ part_draw_dsc.draw_area = &knob_area;
+ part_draw_dsc.rect_dsc = &knob_rect_dsc;
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+
+ lv_draw_rect(draw_ctx, &knob_rect_dsc, &knob_area);
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+}
+
+static void inv_arc_area(lv_obj_t * obj, uint16_t start_angle, uint16_t end_angle, lv_part_t part)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ /*Skip this complicated invalidation if the arc is not visible*/
+ if(lv_obj_is_visible(obj) == false) return;
+
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ if(start_angle == end_angle) return;
+
+ if(start_angle > 360) start_angle -= 360;
+ if(end_angle > 360) end_angle -= 360;
+
+ start_angle += arc->rotation;
+ end_angle += arc->rotation;
+
+ if(start_angle > 360) start_angle -= 360;
+ if(end_angle > 360) end_angle -= 360;
+
+ lv_coord_t r;
+ lv_point_t c;
+ get_center(obj, &c, &r);
+
+ lv_coord_t w = lv_obj_get_style_arc_width(obj, part);
+ lv_coord_t rounded = lv_obj_get_style_arc_rounded(obj, part);
+
+ lv_area_t inv_area;
+ lv_draw_arc_get_area(c.x, c.y, r, start_angle, end_angle, w, rounded, &inv_area);
+ lv_obj_invalidate_area(obj, &inv_area);
+}
+
+static void inv_knob_area(lv_obj_t * obj)
+{
+ lv_point_t c;
+ lv_coord_t r;
+ get_center(obj, &c, &r);
+
+ lv_area_t a;
+ get_knob_area(obj, &c, r, &a);
+ lv_obj_invalidate_area(obj, &a);
+}
+
+static void get_center(const lv_obj_t * obj, lv_point_t * center, lv_coord_t * arc_r)
+{
+ lv_coord_t left_bg = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t right_bg = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+ lv_coord_t top_bg = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t bottom_bg = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
+
+ lv_coord_t r = (LV_MIN(lv_obj_get_width(obj) - left_bg - right_bg,
+ lv_obj_get_height(obj) - top_bg - bottom_bg)) / 2;
+
+ center->x = obj->coords.x1 + r + left_bg;
+ center->y = obj->coords.y1 + r + top_bg;
+
+ if(arc_r) *arc_r = r;
+}
+
+static lv_coord_t get_angle(const lv_obj_t * obj)
+{
+ lv_arc_t * arc = (lv_arc_t *)obj;
+ uint16_t angle = arc->rotation;
+ if(arc->type == LV_ARC_MODE_NORMAL) {
+ angle += arc->indic_angle_end;
+ }
+ else if(arc->type == LV_ARC_MODE_REVERSE) {
+ angle += arc->indic_angle_start;
+ }
+ else if(arc->type == LV_ARC_MODE_SYMMETRICAL) {
+ int16_t bg_end = arc->bg_angle_end;
+ if(arc->bg_angle_end < arc->bg_angle_start) bg_end = arc->bg_angle_end + 360;
+ int16_t indic_end = arc->indic_angle_end;
+ if(arc->indic_angle_end < arc->indic_angle_start) indic_end = arc->indic_angle_end + 360;
+
+ int32_t angle_midpoint = (int32_t)(arc->bg_angle_start + bg_end) / 2;
+ if(arc->indic_angle_start < angle_midpoint) angle += arc->indic_angle_start;
+ else if(indic_end > angle_midpoint) angle += arc->indic_angle_end;
+ else angle += angle_midpoint;
+ }
+
+ return angle;
+}
+
+
+static void get_knob_area(lv_obj_t * obj, const lv_point_t * center, lv_coord_t r, lv_area_t * knob_area)
+{
+ lv_coord_t indic_width = lv_obj_get_style_arc_width(obj, LV_PART_INDICATOR);
+ lv_coord_t indic_width_half = indic_width / 2;
+ r -= indic_width_half;
+
+ lv_coord_t angle = get_angle(obj);
+ lv_coord_t knob_x = (r * lv_trigo_sin(angle + 90)) >> LV_TRIGO_SHIFT;
+ lv_coord_t knob_y = (r * lv_trigo_sin(angle)) >> LV_TRIGO_SHIFT;
+
+ lv_coord_t left_knob = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
+ lv_coord_t right_knob = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
+ lv_coord_t top_knob = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
+ lv_coord_t bottom_knob = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
+
+ knob_area->x1 = center->x + knob_x - left_knob - indic_width_half;
+ knob_area->x2 = center->x + knob_x + right_knob + indic_width_half;
+ knob_area->y1 = center->y + knob_y - top_knob - indic_width_half;
+ knob_area->y2 = center->y + knob_y + bottom_knob + indic_width_half;
+}
+
+/**
+ * Used internally to update arc angles after a value change
+ * @param arc pointer to an arc object
+ */
+static void value_update(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_arc_t * arc = (lv_arc_t *)obj;
+
+ /*If the value is still not set to any value do not update*/
+ if(arc->value == VALUE_UNSET) return;
+
+ int16_t bg_midpoint, range_midpoint, bg_end = arc->bg_angle_end;
+ if(arc->bg_angle_end < arc->bg_angle_start) bg_end = arc->bg_angle_end + 360;
+
+ int16_t angle;
+ switch(arc->type) {
+ case LV_ARC_MODE_SYMMETRICAL:
+ bg_midpoint = (arc->bg_angle_start + bg_end) / 2;
+ range_midpoint = (int32_t)(arc->min_value + arc->max_value) / 2;
+
+ if(arc->value < range_midpoint) {
+ angle = lv_map(arc->value, arc->min_value, range_midpoint, arc->bg_angle_start, bg_midpoint);
+ lv_arc_set_start_angle(obj, angle);
+ lv_arc_set_end_angle(obj, bg_midpoint);
+ }
+ else {
+ angle = lv_map(arc->value, range_midpoint, arc->max_value, bg_midpoint, bg_end);
+ lv_arc_set_start_angle(obj, bg_midpoint);
+ lv_arc_set_end_angle(obj, angle);
+ }
+ break;
+ case LV_ARC_MODE_REVERSE:
+ angle = lv_map(arc->value, arc->min_value, arc->max_value, bg_end, arc->bg_angle_start);
+ lv_arc_set_angles(obj, angle, arc->bg_angle_end);
+ break;
+ case LV_ARC_MODE_NORMAL:
+ angle = lv_map(arc->value, arc->min_value, arc->max_value, arc->bg_angle_start, bg_end);
+ lv_arc_set_angles(obj, arc->bg_angle_start, angle);
+
+ break;
+ default:
+ LV_LOG_WARN("Invalid mode: %d", arc->type);
+ return;
+ }
+ arc->last_angle = angle; /*Cache angle for slew rate limiting*/
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_arc.h b/lib/lvgl/src/widgets/lv_arc.h
new file mode 100644
index 00000000..fd53fc15
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_arc.h
@@ -0,0 +1,256 @@
+/**
+ * @file lv_arc.h
+ *
+ */
+
+#ifndef LV_ARC_H
+#define LV_ARC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_ARC != 0
+
+#include "../core/lv_obj.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+enum {
+ LV_ARC_MODE_NORMAL,
+ LV_ARC_MODE_SYMMETRICAL,
+ LV_ARC_MODE_REVERSE
+};
+typedef uint8_t lv_arc_mode_t;
+
+typedef struct {
+ lv_obj_t obj;
+ uint16_t rotation;
+ uint16_t indic_angle_start;
+ uint16_t indic_angle_end;
+ uint16_t bg_angle_start;
+ uint16_t bg_angle_end;
+ int16_t value; /*Current value of the arc*/
+ int16_t min_value; /*Minimum value of the arc*/
+ int16_t max_value; /*Maximum value of the arc*/
+ uint16_t dragging : 1;
+ uint16_t type : 2;
+ uint16_t min_close : 1; /*1: the last pressed angle was closer to minimum end*/
+ uint16_t chg_rate; /*Drag angle rate of change of the arc (degrees/sec)*/
+ uint32_t last_tick; /*Last dragging event timestamp of the arc*/
+ int16_t last_angle; /*Last dragging angle of the arc*/
+} lv_arc_t;
+
+extern const lv_obj_class_t lv_arc_class;
+
+/**
+ * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_arc_class`
+ * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END`
+ */
+typedef enum {
+ LV_ARC_DRAW_PART_BACKGROUND, /**< The background arc*/
+ LV_ARC_DRAW_PART_FOREGROUND, /**< The foreground arc*/
+ LV_ARC_DRAW_PART_KNOB, /**< The knob*/
+} lv_arc_draw_part_type_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create an arc object
+ * @param parent pointer to an object, it will be the parent of the new arc
+ * @return pointer to the created arc
+ */
+lv_obj_t * lv_arc_create(lv_obj_t * parent);
+
+/*======================
+ * Add/remove functions
+ *=====================*/
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set the start angle of an arc. 0 deg: right, 90 bottom, etc.
+ * @param obj pointer to an arc object
+ * @param start the start angle
+ */
+void lv_arc_set_start_angle(lv_obj_t * obj, uint16_t start);
+
+/**
+ * Set the end angle of an arc. 0 deg: right, 90 bottom, etc.
+ * @param obj pointer to an arc object
+ * @param end the end angle
+ */
+void lv_arc_set_end_angle(lv_obj_t * obj, uint16_t end);
+
+/**
+ * Set the start and end angles
+ * @param obj pointer to an arc object
+ * @param start the start angle
+ * @param end the end angle
+ */
+void lv_arc_set_angles(lv_obj_t * obj, uint16_t start, uint16_t end);
+
+/**
+ * Set the start angle of an arc background. 0 deg: right, 90 bottom, etc.
+ * @param obj pointer to an arc object
+ * @param start the start angle
+ */
+void lv_arc_set_bg_start_angle(lv_obj_t * obj, uint16_t start);
+
+/**
+ * Set the start angle of an arc background. 0 deg: right, 90 bottom etc.
+ * @param obj pointer to an arc object
+ * @param end the end angle
+ */
+void lv_arc_set_bg_end_angle(lv_obj_t * obj, uint16_t end);
+
+/**
+ * Set the start and end angles of the arc background
+ * @param obj pointer to an arc object
+ * @param start the start angle
+ * @param end the end angle
+ */
+void lv_arc_set_bg_angles(lv_obj_t * obj, uint16_t start, uint16_t end);
+
+/**
+ * Set the rotation for the whole arc
+ * @param obj pointer to an arc object
+ * @param rotation rotation angle
+ */
+void lv_arc_set_rotation(lv_obj_t * obj, uint16_t rotation);
+
+/**
+ * Set the type of arc.
+ * @param obj pointer to arc object
+ * @param mode arc's mode
+ */
+void lv_arc_set_mode(lv_obj_t * obj, lv_arc_mode_t type);
+
+/**
+ * Set a new value on the arc
+ * @param obj pointer to an arc object
+ * @param value new value
+ */
+void lv_arc_set_value(lv_obj_t * obj, int16_t value);
+
+/**
+ * Set minimum and the maximum values of an arc
+ * @param obj pointer to the arc object
+ * @param min minimum value
+ * @param max maximum value
+ */
+void lv_arc_set_range(lv_obj_t * obj, int16_t min, int16_t max);
+
+/**
+ * Set a change rate to limit the speed how fast the arc should reach the pressed point.
+ * @param obj pointer to an arc object
+ * @param rate the change rate
+ */
+void lv_arc_set_change_rate(lv_obj_t * obj, uint16_t rate);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the start angle of an arc.
+ * @param obj pointer to an arc object
+ * @return the start angle [0..360]
+ */
+uint16_t lv_arc_get_angle_start(lv_obj_t * obj);
+
+/**
+ * Get the end angle of an arc.
+ * @param obj pointer to an arc object
+ * @return the end angle [0..360]
+ */
+uint16_t lv_arc_get_angle_end(lv_obj_t * obj);
+
+/**
+ * Get the start angle of an arc background.
+ * @param obj pointer to an arc object
+ * @return the start angle [0..360]
+ */
+uint16_t lv_arc_get_bg_angle_start(lv_obj_t * obj);
+
+/**
+ * Get the end angle of an arc background.
+ * @param obj pointer to an arc object
+ * @return the end angle [0..360]
+ */
+uint16_t lv_arc_get_bg_angle_end(lv_obj_t * obj);
+
+/**
+ * Get the value of an arc
+ * @param obj pointer to an arc object
+ * @return the value of the arc
+ */
+int16_t lv_arc_get_value(const lv_obj_t * obj);
+
+/**
+ * Get the minimum value of an arc
+ * @param obj pointer to an arc object
+ * @return the minimum value of the arc
+ */
+int16_t lv_arc_get_min_value(const lv_obj_t * obj);
+
+/**
+ * Get the maximum value of an arc
+ * @param obj pointer to an arc object
+ * @return the maximum value of the arc
+ */
+int16_t lv_arc_get_max_value(const lv_obj_t * obj);
+
+/**
+ * Get whether the arc is type or not.
+ * @param obj pointer to an arc object
+ * @return arc's mode
+ */
+lv_arc_mode_t lv_arc_get_mode(const lv_obj_t * obj);
+
+/*=====================
+ * Other functions
+ *====================*/
+
+/**
+ * Align an object to the current position of the arc (knob)
+ * @param obj pointer to an arc object
+ * @param obj_to_align pointer to an object to align
+ * @param r_offset consider the radius larger with this value (< 0: for smaller radius)
+ */
+void lv_arc_align_obj_to_angle(const lv_obj_t * obj, lv_obj_t * obj_to_align, lv_coord_t r_offset);
+
+/**
+ * Rotate an object to the current position of the arc (knob)
+ * @param obj pointer to an arc object
+ * @param obj_to_align pointer to an object to rotate
+ * @param r_offset consider the radius larger with this value (< 0: for smaller radius)
+ */
+void lv_arc_rotate_obj_to_angle(const lv_obj_t * obj, lv_obj_t * obj_to_rotate, lv_coord_t r_offset);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_ARC*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_ARC_H*/
diff --git a/lib/lvgl/src/widgets/lv_bar.c b/lib/lvgl/src/widgets/lv_bar.c
new file mode 100644
index 00000000..0da2a987
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_bar.c
@@ -0,0 +1,611 @@
+/**
+ * @file lv_bar.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_bar.h"
+#if LV_USE_BAR != 0
+
+#include "../misc/lv_assert.h"
+#include "../draw/lv_draw.h"
+#include "../misc/lv_anim.h"
+#include "../misc/lv_math.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_bar_class
+
+/** hor. pad and ver. pad cannot make the indicator smaller than this [px]*/
+#define LV_BAR_SIZE_MIN 4
+
+#define LV_BAR_IS_ANIMATING(anim_struct) (((anim_struct).anim_state) != LV_BAR_ANIM_STATE_INV)
+#define LV_BAR_GET_ANIM_VALUE(orig_value, anim_struct) (LV_BAR_IS_ANIMATING(anim_struct) ? ((anim_struct).anim_end) : (orig_value))
+
+/** Bar animation start value. (Not the real value of the Bar just indicates process animation)*/
+#define LV_BAR_ANIM_STATE_START 0
+
+/** Bar animation end value. (Not the real value of the Bar just indicates process animation)*/
+#define LV_BAR_ANIM_STATE_END 256
+
+/** Mark no animation is in progress*/
+#define LV_BAR_ANIM_STATE_INV -1
+
+/** log2(LV_BAR_ANIM_STATE_END) used to normalize data*/
+#define LV_BAR_ANIM_STATE_NORM 8
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_bar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_bar_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_bar_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void draw_indic(lv_event_t * e);
+static void lv_bar_set_value_with_anim(lv_obj_t * obj, int32_t new_value, int32_t * value_ptr,
+ _lv_bar_anim_t * anim_info, lv_anim_enable_t en);
+static void lv_bar_init_anim(lv_obj_t * bar, _lv_bar_anim_t * bar_anim);
+static void lv_bar_anim(void * bar, int32_t value);
+static void lv_bar_anim_ready(lv_anim_t * a);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_bar_class = {
+ .constructor_cb = lv_bar_constructor,
+ .destructor_cb = lv_bar_destructor,
+ .event_cb = lv_bar_event,
+ .width_def = LV_DPI_DEF * 2,
+ .height_def = LV_DPI_DEF / 10,
+ .instance_size = sizeof(lv_bar_t),
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_bar_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
+ *====================*/
+
+void lv_bar_set_value(lv_obj_t * obj, int32_t value, lv_anim_enable_t anim)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ if(bar->cur_value == value) return;
+
+ value = LV_CLAMP(bar->min_value, value, bar->max_value);
+ value = value < bar->start_value ? bar->start_value : value; /*Can't be smaller than the left value*/
+
+ if(bar->cur_value == value) return;
+ lv_bar_set_value_with_anim(obj, value, &bar->cur_value, &bar->cur_value_anim, anim);
+}
+
+void lv_bar_set_start_value(lv_obj_t * obj, int32_t value, lv_anim_enable_t anim)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ if(bar->mode != LV_BAR_MODE_RANGE) {
+ return;
+ }
+
+ value = LV_CLAMP(bar->min_value, value, bar->max_value);
+ value = value > bar->cur_value ? bar->cur_value : value; /*Can't be greater than the right value*/
+
+ if(bar->start_value == value) return;
+ lv_bar_set_value_with_anim(obj, value, &bar->start_value, &bar->start_value_anim, anim);
+}
+
+void lv_bar_set_range(lv_obj_t * obj, int32_t min, int32_t max)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ if(bar->min_value == min && bar->max_value == max) return;
+
+ bar->max_value = max;
+ bar->min_value = min;
+
+ if(lv_bar_get_mode(obj) != LV_BAR_MODE_RANGE)
+ bar->start_value = min;
+
+ if(bar->cur_value > max) {
+ bar->cur_value = max;
+ lv_bar_set_value(obj, bar->cur_value, LV_ANIM_OFF);
+ }
+ if(bar->cur_value < min) {
+ bar->cur_value = min;
+ lv_bar_set_value(obj, bar->cur_value, LV_ANIM_OFF);
+ }
+ lv_obj_invalidate(obj);
+}
+
+void lv_bar_set_mode(lv_obj_t * obj, lv_bar_mode_t mode)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ bar->mode = mode;
+ if(bar->mode != LV_BAR_MODE_RANGE) {
+ bar->start_value = bar->min_value;
+ }
+
+ lv_obj_invalidate(obj);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+int32_t lv_bar_get_value(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ return LV_BAR_GET_ANIM_VALUE(bar->cur_value, bar->cur_value_anim);
+}
+
+int32_t lv_bar_get_start_value(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ if(bar->mode != LV_BAR_MODE_RANGE) return bar->min_value;
+
+ return LV_BAR_GET_ANIM_VALUE(bar->start_value, bar->start_value_anim);
+}
+
+int32_t lv_bar_get_min_value(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_bar_t * bar = (lv_bar_t *)obj;
+ return bar->min_value;
+}
+
+int32_t lv_bar_get_max_value(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ return bar->max_value;
+}
+
+lv_bar_mode_t lv_bar_get_mode(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ return bar->mode;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_bar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_bar_t * bar = (lv_bar_t *)obj;
+ bar->min_value = 0;
+ bar->max_value = 100;
+ bar->start_value = 0;
+ bar->cur_value = 0;
+ bar->indic_area.x1 = 0;
+ bar->indic_area.x2 = 0;
+ bar->indic_area.y1 = 0;
+ bar->indic_area.y2 = 0;
+ bar->mode = LV_BAR_MODE_NORMAL;
+
+ lv_bar_init_anim(obj, &bar->cur_value_anim);
+ lv_bar_init_anim(obj, &bar->start_value_anim);
+
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_CHECKABLE);
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
+ lv_bar_set_value(obj, 0, LV_ANIM_OFF);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_bar_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ lv_anim_del(&bar->cur_value_anim, NULL);
+ lv_anim_del(&bar->start_value_anim, NULL);
+}
+
+static void draw_indic(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ lv_area_t bar_coords;
+ lv_obj_get_coords(obj, &bar_coords);
+
+ lv_coord_t transf_w = lv_obj_get_style_transform_width(obj, LV_PART_MAIN);
+ lv_coord_t transf_h = lv_obj_get_style_transform_height(obj, LV_PART_MAIN);
+ bar_coords.x1 -= transf_w;
+ bar_coords.x2 += transf_w;
+ bar_coords.y1 -= transf_h;
+ bar_coords.y2 += transf_h;
+ lv_coord_t barw = lv_area_get_width(&bar_coords);
+ lv_coord_t barh = lv_area_get_height(&bar_coords);
+ int32_t range = bar->max_value - bar->min_value;
+ bool hor = barw >= barh ? true : false;
+ bool sym = false;
+ if(bar->mode == LV_BAR_MODE_SYMMETRICAL && bar->min_value < 0 && bar->max_value > 0 &&
+ bar->start_value == bar->min_value) sym = true;
+
+ /*Calculate the indicator area*/
+ lv_coord_t bg_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t bg_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+ lv_coord_t bg_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t bg_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
+ /*Respect padding and minimum width/height too*/
+ lv_area_copy(&bar->indic_area, &bar_coords);
+ bar->indic_area.x1 += bg_left;
+ bar->indic_area.x2 -= bg_right;
+ bar->indic_area.y1 += bg_top;
+ bar->indic_area.y2 -= bg_bottom;
+
+ if(hor && lv_area_get_height(&bar->indic_area) < LV_BAR_SIZE_MIN) {
+ bar->indic_area.y1 = obj->coords.y1 + (barh / 2) - (LV_BAR_SIZE_MIN / 2);
+ bar->indic_area.y2 = bar->indic_area.y1 + LV_BAR_SIZE_MIN;
+ }
+ else if(!hor && lv_area_get_width(&bar->indic_area) < LV_BAR_SIZE_MIN) {
+ bar->indic_area.x1 = obj->coords.x1 + (barw / 2) - (LV_BAR_SIZE_MIN / 2);
+ bar->indic_area.x2 = bar->indic_area.x1 + LV_BAR_SIZE_MIN;
+ }
+
+ lv_coord_t indicw = lv_area_get_width(&bar->indic_area);
+ lv_coord_t indich = lv_area_get_height(&bar->indic_area);
+
+ /*Calculate the indicator length*/
+ lv_coord_t anim_length = hor ? indicw : indich;
+
+ lv_coord_t anim_cur_value_x, anim_start_value_x;
+
+ lv_coord_t * axis1, * axis2;
+ lv_coord_t (*indic_length_calc)(const lv_area_t * area);
+
+ if(hor) {
+ axis1 = &bar->indic_area.x1;
+ axis2 = &bar->indic_area.x2;
+ indic_length_calc = lv_area_get_width;
+ }
+ else {
+ axis1 = &bar->indic_area.y1;
+ axis2 = &bar->indic_area.y2;
+ indic_length_calc = lv_area_get_height;
+ }
+
+ if(LV_BAR_IS_ANIMATING(bar->start_value_anim)) {
+ lv_coord_t anim_start_value_start_x =
+ (int32_t)((int32_t)anim_length * (bar->start_value_anim.anim_start - bar->min_value)) / range;
+ lv_coord_t anim_start_value_end_x =
+ (int32_t)((int32_t)anim_length * (bar->start_value_anim.anim_end - bar->min_value)) / range;
+
+ anim_start_value_x = (((anim_start_value_end_x - anim_start_value_start_x) * bar->start_value_anim.anim_state) /
+ LV_BAR_ANIM_STATE_END);
+
+ anim_start_value_x += anim_start_value_start_x;
+ }
+ else {
+ anim_start_value_x = (int32_t)((int32_t)anim_length * (bar->start_value - bar->min_value)) / range;
+ }
+
+ if(LV_BAR_IS_ANIMATING(bar->cur_value_anim)) {
+ lv_coord_t anim_cur_value_start_x =
+ (int32_t)((int32_t)anim_length * (bar->cur_value_anim.anim_start - bar->min_value)) / range;
+ lv_coord_t anim_cur_value_end_x =
+ (int32_t)((int32_t)anim_length * (bar->cur_value_anim.anim_end - bar->min_value)) / range;
+
+ anim_cur_value_x = anim_cur_value_start_x + (((anim_cur_value_end_x - anim_cur_value_start_x) *
+ bar->cur_value_anim.anim_state) /
+ LV_BAR_ANIM_STATE_END);
+ }
+ else {
+ anim_cur_value_x = (int32_t)((int32_t)anim_length * (bar->cur_value - bar->min_value)) / range;
+ }
+
+ lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
+ if(hor && base_dir == LV_BASE_DIR_RTL) {
+ /*Swap axes*/
+ lv_coord_t * tmp;
+ tmp = axis1;
+ axis1 = axis2;
+ axis2 = tmp;
+ anim_cur_value_x = -anim_cur_value_x;
+ anim_start_value_x = -anim_start_value_x;
+ }
+
+ /*Set the indicator length*/
+ if(hor) {
+ *axis2 = *axis1 + anim_cur_value_x;
+ *axis1 += anim_start_value_x;
+ }
+ else {
+ *axis1 = *axis2 - anim_cur_value_x + 1;
+ *axis2 -= anim_start_value_x;
+ }
+ if(sym) {
+ lv_coord_t zero, shift;
+ shift = (-bar->min_value * anim_length) / range;
+ if(hor) {
+ zero = *axis1 + shift;
+ if(*axis2 > zero)
+ *axis1 = zero;
+ else {
+ *axis1 = *axis2;
+ *axis2 = zero;
+ }
+ }
+ else {
+ zero = *axis2 - shift + 1;
+ if(*axis1 > zero)
+ *axis2 = zero;
+ else {
+ *axis2 = *axis1;
+ *axis1 = zero;
+ }
+ if(*axis2 < *axis1) {
+ /*swap*/
+ zero = *axis1;
+ *axis1 = *axis2;
+ *axis2 = zero;
+ }
+ }
+ }
+
+ /*Do not draw a zero length indicator but at least call the draw part events*/
+ if(!sym && indic_length_calc(&bar->indic_area) <= 1) {
+
+ 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_INDICATOR;
+ part_draw_dsc.class_p = MY_CLASS;
+ part_draw_dsc.type = LV_BAR_DRAW_PART_INDICATOR;
+ part_draw_dsc.draw_area = &bar->indic_area;
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+ return;
+ }
+
+ lv_area_t indic_area;
+ lv_area_copy(&indic_area, &bar->indic_area);
+
+ lv_draw_rect_dsc_t draw_rect_dsc;
+ lv_draw_rect_dsc_init(&draw_rect_dsc);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &draw_rect_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_INDICATOR;
+ part_draw_dsc.class_p = MY_CLASS;
+ part_draw_dsc.type = LV_BAR_DRAW_PART_INDICATOR;
+ part_draw_dsc.rect_dsc = &draw_rect_dsc;
+ part_draw_dsc.draw_area = &bar->indic_area;
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+
+ lv_coord_t bg_radius = lv_obj_get_style_radius(obj, LV_PART_MAIN);
+ lv_coord_t short_side = LV_MIN(barw, barh);
+ if(bg_radius > short_side >> 1) bg_radius = short_side >> 1;
+
+ lv_coord_t indic_radius = draw_rect_dsc.radius;
+ short_side = LV_MIN(indicw, indich);
+ if(indic_radius > short_side >> 1) indic_radius = short_side >> 1;
+
+ /*Draw only the shadow and outline only if the indicator is long enough.
+ *The radius of the bg and the indicator can make a strange shape where
+ *it'd be very difficult to draw shadow.*/
+ if((hor && lv_area_get_width(&bar->indic_area) > indic_radius * 2) ||
+ (!hor && lv_area_get_height(&bar->indic_area) > indic_radius * 2)) {
+ lv_opa_t bg_opa = draw_rect_dsc.bg_opa;
+ lv_opa_t bg_img_opa = draw_rect_dsc.bg_img_opa;
+ lv_opa_t border_opa = draw_rect_dsc.border_opa;
+ draw_rect_dsc.bg_opa = LV_OPA_TRANSP;
+ draw_rect_dsc.bg_img_opa = LV_OPA_TRANSP;
+ draw_rect_dsc.border_opa = LV_OPA_TRANSP;
+
+ lv_draw_rect(draw_ctx, &draw_rect_dsc, &bar->indic_area);
+
+ draw_rect_dsc.bg_opa = bg_opa;
+ draw_rect_dsc.bg_img_opa = bg_img_opa;
+ draw_rect_dsc.border_opa = border_opa;
+ }
+
+#if LV_DRAW_COMPLEX
+ lv_draw_mask_radius_param_t mask_bg_param;
+ lv_area_t bg_mask_area;
+ bg_mask_area.x1 = obj->coords.x1 + bg_left;
+ bg_mask_area.x2 = obj->coords.x2 - bg_right;
+ bg_mask_area.y1 = obj->coords.y1 + bg_top;
+ bg_mask_area.y2 = obj->coords.y2 - bg_bottom;
+
+ lv_draw_mask_radius_init(&mask_bg_param, &bg_mask_area, bg_radius, false);
+ lv_coord_t mask_bg_id = lv_draw_mask_add(&mask_bg_param, NULL);
+#endif
+
+ /*Draw_only the background and background image*/
+ lv_opa_t shadow_opa = draw_rect_dsc.shadow_opa;
+ lv_opa_t border_opa = draw_rect_dsc.border_opa;
+ draw_rect_dsc.border_opa = LV_OPA_TRANSP;
+ draw_rect_dsc.shadow_opa = LV_OPA_TRANSP;
+
+ /*Get the max possible indicator area. The gradient should be applied on this*/
+ lv_area_t mask_indic_max_area;
+ lv_area_copy(&mask_indic_max_area, &bar_coords);
+ mask_indic_max_area.x1 += bg_left;
+ mask_indic_max_area.y1 += bg_top;
+ mask_indic_max_area.x2 -= bg_right;
+ mask_indic_max_area.y2 -= bg_bottom;
+ if(hor && lv_area_get_height(&mask_indic_max_area) < LV_BAR_SIZE_MIN) {
+ mask_indic_max_area.y1 = obj->coords.y1 + (barh / 2) - (LV_BAR_SIZE_MIN / 2);
+ mask_indic_max_area.y2 = mask_indic_max_area.y1 + LV_BAR_SIZE_MIN;
+ }
+ else if(!hor && lv_area_get_width(&mask_indic_max_area) < LV_BAR_SIZE_MIN) {
+ mask_indic_max_area.x1 = obj->coords.x1 + (barw / 2) - (LV_BAR_SIZE_MIN / 2);
+ mask_indic_max_area.x2 = mask_indic_max_area.x1 + LV_BAR_SIZE_MIN;
+ }
+
+#if LV_DRAW_COMPLEX
+ /*Create a mask to the current indicator area to see only this part from the whole gradient.*/
+ lv_draw_mask_radius_param_t mask_indic_param;
+ lv_draw_mask_radius_init(&mask_indic_param, &bar->indic_area, draw_rect_dsc.radius, false);
+ int16_t mask_indic_id = lv_draw_mask_add(&mask_indic_param, NULL);
+#endif
+
+ lv_draw_rect(draw_ctx, &draw_rect_dsc, &mask_indic_max_area);
+ draw_rect_dsc.border_opa = border_opa;
+ draw_rect_dsc.shadow_opa = shadow_opa;
+
+ /*Draw the border*/
+ draw_rect_dsc.bg_opa = LV_OPA_TRANSP;
+ draw_rect_dsc.bg_img_opa = LV_OPA_TRANSP;
+ draw_rect_dsc.shadow_opa = LV_OPA_TRANSP;
+ lv_draw_rect(draw_ctx, &draw_rect_dsc, &bar->indic_area);
+
+#if LV_DRAW_COMPLEX
+ lv_draw_mask_free_param(&mask_indic_param);
+ lv_draw_mask_free_param(&mask_bg_param);
+ lv_draw_mask_remove_id(mask_indic_id);
+ lv_draw_mask_remove_id(mask_bg_id);
+#endif
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+}
+
+static void lv_bar_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*/
+ 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_REFR_EXT_DRAW_SIZE) {
+ lv_coord_t indic_size;
+ indic_size = lv_obj_calculate_ext_draw_size(obj, LV_PART_INDICATOR);
+
+ /*Bg size is handled by lv_obj*/
+ lv_coord_t * s = lv_event_get_param(e);
+ *s = LV_MAX(*s, indic_size);
+
+ /*Calculate the indicator area*/
+ lv_coord_t bg_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t bg_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+ lv_coord_t bg_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t bg_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
+
+ lv_coord_t pad = LV_MIN4(bg_left, bg_right, bg_top, bg_bottom);
+ if(pad < 0) {
+ *s = LV_MAX(*s, -pad);
+ }
+ }
+ else if(code == LV_EVENT_PRESSED || code == LV_EVENT_RELEASED) {
+ lv_bar_t * bar = (lv_bar_t *)obj;
+ lv_obj_invalidate_area(obj, &bar->indic_area);
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ draw_indic(e);
+ }
+}
+
+static void lv_bar_anim(void * var, int32_t value)
+{
+ _lv_bar_anim_t * bar_anim = var;
+ bar_anim->anim_state = value;
+ lv_obj_invalidate(bar_anim->bar);
+}
+
+static void lv_bar_anim_ready(lv_anim_t * a)
+{
+ _lv_bar_anim_t * var = a->var;
+ lv_obj_t * obj = (lv_obj_t *)var->bar;
+ lv_bar_t * bar = (lv_bar_t *)obj;
+
+ var->anim_state = LV_BAR_ANIM_STATE_INV;
+ if(var == &bar->cur_value_anim)
+ bar->cur_value = var->anim_end;
+ else if(var == &bar->start_value_anim)
+ bar->start_value = var->anim_end;
+ lv_obj_invalidate(var->bar);
+}
+
+static void lv_bar_set_value_with_anim(lv_obj_t * obj, int32_t new_value, int32_t * value_ptr,
+ _lv_bar_anim_t * anim_info, lv_anim_enable_t en)
+{
+ if(en == LV_ANIM_OFF) {
+ *value_ptr = new_value;
+ lv_obj_invalidate((lv_obj_t *)obj);
+ }
+ else {
+ /*No animation in progress -> simply set the values*/
+ if(anim_info->anim_state == LV_BAR_ANIM_STATE_INV) {
+ anim_info->anim_start = *value_ptr;
+ anim_info->anim_end = new_value;
+ }
+ /*Animation in progress. Start from the animation end value*/
+ else {
+ anim_info->anim_start = anim_info->anim_end;
+ anim_info->anim_end = new_value;
+ }
+ *value_ptr = new_value;
+ /*Stop the previous animation if it exists*/
+ lv_anim_del(anim_info, NULL);
+
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, anim_info);
+ lv_anim_set_exec_cb(&a, lv_bar_anim);
+ lv_anim_set_values(&a, LV_BAR_ANIM_STATE_START, LV_BAR_ANIM_STATE_END);
+ lv_anim_set_ready_cb(&a, lv_bar_anim_ready);
+ lv_anim_set_time(&a, lv_obj_get_style_anim_time(obj, LV_PART_MAIN));
+ lv_anim_start(&a);
+ }
+}
+
+static void lv_bar_init_anim(lv_obj_t * obj, _lv_bar_anim_t * bar_anim)
+{
+ bar_anim->bar = obj;
+ bar_anim->anim_start = 0;
+ bar_anim->anim_end = 0;
+ bar_anim->anim_state = LV_BAR_ANIM_STATE_INV;
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_bar.h b/lib/lvgl/src/widgets/lv_bar.h
new file mode 100644
index 00000000..1726425b
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_bar.h
@@ -0,0 +1,164 @@
+/**
+ * @file lv_bar.h
+ *
+ */
+
+#ifndef LV_BAR_H
+#define LV_BAR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_BAR != 0
+
+#include "../core/lv_obj.h"
+#include "../misc/lv_anim.h"
+#include "lv_btn.h"
+#include "lv_label.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+enum {
+ LV_BAR_MODE_NORMAL,
+ LV_BAR_MODE_SYMMETRICAL,
+ LV_BAR_MODE_RANGE
+};
+typedef uint8_t lv_bar_mode_t;
+
+typedef struct {
+ lv_obj_t * bar;
+ int32_t anim_start;
+ int32_t anim_end;
+ int32_t anim_state;
+} _lv_bar_anim_t;
+
+typedef struct {
+ lv_obj_t obj;
+ int32_t cur_value; /**< Current value of the bar*/
+ int32_t min_value; /**< Minimum value of the bar*/
+ int32_t max_value; /**< Maximum value of the bar*/
+ int32_t start_value; /**< Start value of the bar*/
+ lv_area_t indic_area; /**< Save the indicator area. Might be used by derived types*/
+ _lv_bar_anim_t cur_value_anim;
+ _lv_bar_anim_t start_value_anim;
+ lv_bar_mode_t mode : 2; /**< Type of bar*/
+} lv_bar_t;
+
+extern const lv_obj_class_t lv_bar_class;
+
+/**
+ * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_bar_class`
+ * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END`
+ */
+typedef enum {
+ LV_BAR_DRAW_PART_INDICATOR, /**< The indicator*/
+} lv_bar_draw_part_type_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a bar object
+ * @param parent pointer to an object, it will be the parent of the new bar
+ * @return pointer to the created bar
+ */
+lv_obj_t * lv_bar_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set a new value on the bar
+ * @param bar pointer to a bar object
+ * @param value new value
+ * @param anim LV_ANIM_ON: set the value with an animation; LV_ANIM_OFF: change the value immediately
+ */
+void lv_bar_set_value(lv_obj_t * obj, int32_t value, lv_anim_enable_t anim);
+
+/**
+ * Set a new start value on the bar
+ * @param obj pointer to a bar object
+ * @param value new start value
+ * @param anim LV_ANIM_ON: set the value with an animation; LV_ANIM_OFF: change the value immediately
+ */
+void lv_bar_set_start_value(lv_obj_t * obj, int32_t start_value, lv_anim_enable_t anim);
+
+/**
+ * Set minimum and the maximum values of a bar
+ * @param obj pointer to the bar object
+ * @param min minimum value
+ * @param max maximum value
+ */
+void lv_bar_set_range(lv_obj_t * obj, int32_t min, int32_t max);
+
+/**
+ * Set the type of bar.
+ * @param obj pointer to bar object
+ * @param mode bar type from ::lv_bar_mode_t
+ */
+void lv_bar_set_mode(lv_obj_t * obj, lv_bar_mode_t mode);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the value of a bar
+ * @param obj pointer to a bar object
+ * @return the value of the bar
+ */
+int32_t lv_bar_get_value(const lv_obj_t * obj);
+
+/**
+ * Get the start value of a bar
+ * @param obj pointer to a bar object
+ * @return the start value of the bar
+ */
+int32_t lv_bar_get_start_value(const lv_obj_t * obj);
+
+/**
+ * Get the minimum value of a bar
+ * @param obj pointer to a bar object
+ * @return the minimum value of the bar
+ */
+int32_t lv_bar_get_min_value(const lv_obj_t * obj);
+
+/**
+ * Get the maximum value of a bar
+ * @param obj pointer to a bar object
+ * @return the maximum value of the bar
+ */
+int32_t lv_bar_get_max_value(const lv_obj_t * obj);
+
+/**
+ * Get the type of bar.
+ * @param obj pointer to bar object
+ * @return bar type from ::lv_bar_mode_t
+ */
+lv_bar_mode_t lv_bar_get_mode(lv_obj_t * obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_BAR*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_BAR_H*/
diff --git a/lib/lvgl/src/widgets/lv_btn.c b/lib/lvgl/src/widgets/lv_btn.c
new file mode 100644
index 00000000..5676dc7a
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_btn.c
@@ -0,0 +1,72 @@
+/**
+ * @file lv_btn.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+
+#include "lv_btn.h"
+#if LV_USE_BTN != 0
+
+#include "../extra/layouts/flex/lv_flex.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_btn_class
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_btn_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_btn_class = {
+ .constructor_cb = lv_btn_constructor,
+ .width_def = LV_SIZE_CONTENT,
+ .height_def = LV_SIZE_CONTENT,
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
+ .instance_size = sizeof(lv_btn_t),
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_btn_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;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_btn_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_btn.h b/lib/lvgl/src/widgets/lv_btn.h
new file mode 100644
index 00000000..1d471f97
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_btn.h
@@ -0,0 +1,56 @@
+/**
+ * @file lv_btn.h
+ *
+ */
+
+#ifndef LV_BTN_H
+#define LV_BTN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_BTN != 0
+#include "../core/lv_obj.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ lv_obj_t obj;
+} lv_btn_t;
+
+extern const lv_obj_class_t lv_btn_class;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a button object
+ * @param parent pointer to an object, it will be the parent of the new button
+ * @return pointer to the created button
+ */
+lv_obj_t * lv_btn_create(lv_obj_t * parent);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_BTN*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_BTN_H*/
diff --git a/lib/lvgl/src/widgets/lv_btnmatrix.c b/lib/lvgl/src/widgets/lv_btnmatrix.c
new file mode 100644
index 00000000..92a4d2fe
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_btnmatrix.c
@@ -0,0 +1,1048 @@
+/**
+ * @file lv_btnmatrix.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_btnmatrix.h"
+#if LV_USE_BTNMATRIX != 0
+
+#include "../misc/lv_assert.h"
+#include "../core/lv_indev.h"
+#include "../core/lv_group.h"
+#include "../draw/lv_draw.h"
+#include "../core/lv_refr.h"
+#include "../misc/lv_txt.h"
+#include "../misc/lv_txt_ap.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_btnmatrix_class
+
+#define BTN_EXTRA_CLICK_AREA_MAX (LV_DPI_DEF / 10)
+#define LV_BTNMATRIX_WIDTH_MASK 0x0007
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_btnmatrix_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_btnmatrix_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_btnmatrix_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void draw_main(lv_event_t * e);
+
+static uint8_t get_button_width(lv_btnmatrix_ctrl_t ctrl_bits);
+static bool button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits);
+static bool button_is_checked(lv_btnmatrix_ctrl_t ctrl_bits);
+static bool button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits);
+static bool button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits);
+static bool button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits);
+static bool button_is_popover(lv_btnmatrix_ctrl_t ctrl_bits);
+static bool button_is_checkable(lv_btnmatrix_ctrl_t ctrl_bits);
+static bool button_is_recolor(lv_btnmatrix_ctrl_t ctrl_bits);
+static bool button_get_checked(lv_btnmatrix_ctrl_t ctrl_bits);
+static uint16_t get_button_from_point(lv_obj_t * obj, lv_point_t * p);
+static void allocate_btn_areas_and_controls(const lv_obj_t * obj, const char ** map);
+static void invalidate_button_area(const lv_obj_t * obj, uint16_t btn_idx);
+static void make_one_button_checked(lv_obj_t * obj, uint16_t btn_idx);
+static bool has_popovers_in_top_row(lv_obj_t * obj);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+static const char * lv_btnmatrix_def_map[] = {"Btn1", "Btn2", "Btn3", "\n", "Btn4", "Btn5", ""};
+
+const lv_obj_class_t lv_btnmatrix_class = {
+ .constructor_cb = lv_btnmatrix_constructor,
+ .destructor_cb = lv_btnmatrix_destructor,
+ .event_cb = lv_btnmatrix_event,
+ .width_def = LV_DPI_DEF * 2,
+ .height_def = LV_DPI_DEF,
+ .instance_size = sizeof(lv_btnmatrix_t),
+ .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_btnmatrix_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
+ *====================*/
+
+void lv_btnmatrix_set_map(lv_obj_t * obj, const char * map[])
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ if(map == NULL) return;
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+
+ /*Analyze the map and create the required number of buttons*/
+ allocate_btn_areas_and_controls(obj, map);
+ btnm->map_p = map;
+
+ lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
+
+ /*Set size and positions of the buttons*/
+ lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t prow = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
+ lv_coord_t pcol = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
+
+ lv_coord_t max_w = lv_obj_get_content_width(obj);
+ lv_coord_t max_h = lv_obj_get_content_height(obj);
+
+ /*Calculate the position of each row*/
+ lv_coord_t max_h_no_gap = max_h - (prow * (btnm->row_cnt - 1));
+
+ /*Count the units and the buttons in a line
+ *(A button can be 1,2,3... unit wide)*/
+ uint32_t txt_tot_i = 0; /*Act. index in the str map*/
+ uint32_t btn_tot_i = 0; /*Act. index of button areas*/
+ const char ** map_row = map;
+
+ /*Count the units and the buttons in a line*/
+ uint32_t row;
+ for(row = 0; row < btnm->row_cnt; row++) {
+ uint16_t unit_cnt = 0; /*Number of units in a row*/
+ uint16_t btn_cnt = 0; /*Number of buttons in a row*/
+ /*Count the buttons and units in this row*/
+ while(map_row[btn_cnt] && strcmp(map_row[btn_cnt], "\n") != 0 && map_row[btn_cnt][0] != '\0') {
+ unit_cnt += get_button_width(btnm->ctrl_bits[btn_tot_i + btn_cnt]);
+ btn_cnt++;
+ }
+
+ /*Only deal with the non empty lines*/
+ if(btn_cnt == 0) {
+ map_row = &map_row[btn_cnt + 1]; /*Set the map to the next row*/
+ continue;
+ }
+
+ lv_coord_t row_y1 = ptop + (max_h_no_gap * row) / btnm->row_cnt + row * prow;
+ lv_coord_t row_y2 = ptop + (max_h_no_gap * (row + 1)) / btnm->row_cnt + row * prow - 1;
+
+ /*Set the button size and positions*/
+ lv_coord_t max_w_no_gap = max_w - (pcol * (btn_cnt - 1));
+ if(max_w_no_gap < 0) max_w_no_gap = 0;
+
+ uint32_t row_unit_cnt = 0; /*The current unit position in the row*/
+ uint32_t btn;
+ for(btn = 0; btn < btn_cnt; btn++, btn_tot_i++, txt_tot_i++) {
+ uint32_t btn_u = get_button_width(btnm->ctrl_bits[btn_tot_i]);
+
+ lv_coord_t btn_x1 = (max_w_no_gap * row_unit_cnt) / unit_cnt + btn * pcol;
+ lv_coord_t btn_x2 = (max_w_no_gap * (row_unit_cnt + btn_u)) / unit_cnt + btn * pcol - 1;
+
+ /*If RTL start from the right*/
+ if(base_dir == LV_BASE_DIR_RTL) {
+ lv_coord_t tmp = btn_x1;
+ btn_x1 = btn_x2;
+ btn_x2 = tmp;
+
+ btn_x1 = max_w - btn_x1;
+ btn_x2 = max_w - btn_x2;
+ }
+
+ btn_x1 += pleft;
+ btn_x2 += pleft;
+
+ lv_area_set(&btnm->button_areas[btn_tot_i], btn_x1, row_y1, btn_x2, row_y2);
+
+ row_unit_cnt += btn_u;
+ }
+
+ map_row = &map_row[btn_cnt + 1]; /*Set the map to the next line*/
+ }
+
+ /*Popovers in the top row will draw outside the widget and the extended draw size depends on
+ *the row height which may have changed when setting the new map*/
+ lv_obj_refresh_ext_draw_size(obj);
+
+ lv_obj_invalidate(obj);
+}
+
+void lv_btnmatrix_set_ctrl_map(lv_obj_t * obj, const lv_btnmatrix_ctrl_t ctrl_map[])
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+ lv_memcpy(btnm->ctrl_bits, ctrl_map, sizeof(lv_btnmatrix_ctrl_t) * btnm->btn_cnt);
+
+ lv_btnmatrix_set_map(obj, btnm->map_p);
+}
+
+void lv_btnmatrix_set_selected_btn(lv_obj_t * obj, uint16_t btn_id)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+
+ if(btn_id >= btnm->btn_cnt && btn_id != LV_BTNMATRIX_BTN_NONE) return;
+
+ invalidate_button_area(obj, btnm->btn_id_sel);
+ btnm->btn_id_sel = btn_id;
+ invalidate_button_area(obj, btn_id);
+}
+
+void lv_btnmatrix_set_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+
+ if(btn_id >= btnm->btn_cnt) return;
+
+ if(btnm->one_check && (ctrl & LV_BTNMATRIX_CTRL_CHECKED)) {
+ lv_btnmatrix_clear_btn_ctrl_all(obj, LV_BTNMATRIX_CTRL_CHECKED);
+ }
+
+ btnm->ctrl_bits[btn_id] |= ctrl;
+ invalidate_button_area(obj, btn_id);
+
+ if(ctrl & LV_BTNMATRIX_CTRL_POPOVER) {
+ lv_obj_refresh_ext_draw_size(obj);
+ }
+}
+
+void lv_btnmatrix_clear_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+
+ if(btn_id >= btnm->btn_cnt) return;
+
+ btnm->ctrl_bits[btn_id] &= (~ctrl);
+ invalidate_button_area(obj, btn_id);
+
+ if(ctrl & LV_BTNMATRIX_CTRL_POPOVER) {
+ lv_obj_refresh_ext_draw_size(obj);
+ }
+}
+
+void lv_btnmatrix_set_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+ uint16_t i;
+ for(i = 0; i < btnm->btn_cnt; i++) {
+ lv_btnmatrix_set_btn_ctrl(obj, i, ctrl);
+ }
+}
+
+void lv_btnmatrix_clear_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+ uint16_t i;
+ for(i = 0; i < btnm->btn_cnt; i++) {
+ lv_btnmatrix_clear_btn_ctrl(obj, i, ctrl);
+ }
+}
+
+void lv_btnmatrix_set_btn_width(lv_obj_t * obj, uint16_t btn_id, uint8_t width)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+ if(btn_id >= btnm->btn_cnt) return;
+ btnm->ctrl_bits[btn_id] &= (~LV_BTNMATRIX_WIDTH_MASK);
+ btnm->ctrl_bits[btn_id] |= (LV_BTNMATRIX_WIDTH_MASK & width);
+
+ lv_btnmatrix_set_map(obj, btnm->map_p);
+}
+
+void lv_btnmatrix_set_one_checked(lv_obj_t * obj, bool en)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+ btnm->one_check = en;
+
+ /*If more than one button is toggled only the first one should be*/
+ make_one_button_checked(obj, 0);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+const char ** lv_btnmatrix_get_map(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+ return btnm->map_p;
+}
+
+uint16_t lv_btnmatrix_get_selected_btn(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+ return btnm->btn_id_sel;
+}
+
+const char * lv_btnmatrix_get_btn_text(const lv_obj_t * obj, uint16_t btn_id)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ if(btn_id == LV_BTNMATRIX_BTN_NONE) return NULL;
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+ if(btn_id > btnm->btn_cnt) return NULL;
+
+ uint16_t txt_i = 0;
+ uint16_t btn_i = 0;
+
+ /*Search the text of btnm->btn_pr the buttons text in the map
+ *Skip "\n"-s*/
+ while(btn_i != btn_id) {
+ btn_i++;
+ txt_i++;
+ if(strcmp(btnm->map_p[txt_i], "\n") == 0) txt_i++;
+ }
+
+ if(btn_i == btnm->btn_cnt) return NULL;
+
+ return btnm->map_p[txt_i];
+}
+
+bool lv_btnmatrix_has_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+ if(btn_id >= btnm->btn_cnt) return false;
+
+ return ((btnm->ctrl_bits[btn_id] & ctrl) == ctrl) ? true : false;
+}
+
+bool lv_btnmatrix_get_one_checked(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+
+ return btnm->one_check;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_btnmatrix_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+ btnm->btn_cnt = 0;
+ btnm->row_cnt = 0;
+ btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
+ btnm->button_areas = NULL;
+ btnm->ctrl_bits = NULL;
+ btnm->map_p = NULL;
+ btnm->one_check = 0;
+
+ lv_btnmatrix_set_map(obj, lv_btnmatrix_def_map);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_btnmatrix_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_TRACE_OBJ_CREATE("begin");
+ LV_UNUSED(class_p);
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+ lv_mem_free(btnm->button_areas);
+ lv_mem_free(btnm->ctrl_bits);
+ btnm->button_areas = NULL;
+ btnm->ctrl_bits = NULL;
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_btnmatrix_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*/
+ 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_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+ lv_point_t p;
+
+ if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
+ if(has_popovers_in_top_row(obj)) {
+ /*reserve one row worth of extra space to account for popovers in the top row*/
+ lv_coord_t s = btnm->row_cnt > 0 ? lv_obj_get_content_height(obj) / btnm->row_cnt : 0;
+ lv_event_set_ext_draw_size(e, s);
+ }
+ }
+ if(code == LV_EVENT_STYLE_CHANGED) {
+ lv_btnmatrix_set_map(obj, btnm->map_p);
+ }
+ else if(code == LV_EVENT_SIZE_CHANGED) {
+ lv_btnmatrix_set_map(obj, btnm->map_p);
+ }
+ else if(code == LV_EVENT_PRESSED) {
+ void * param = lv_event_get_param(e);
+ invalidate_button_area(obj, btnm->btn_id_sel);
+
+ lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
+ if(indev_type == LV_INDEV_TYPE_POINTER || indev_type == LV_INDEV_TYPE_BUTTON) {
+ uint16_t btn_pr;
+ /*Search the pressed area*/
+ lv_indev_get_point(param, &p);
+ btn_pr = get_button_from_point(obj, &p);
+ /*Handle the case where there is no button there*/
+ if(btn_pr != LV_BTNMATRIX_BTN_NONE) {
+ if(button_is_inactive(btnm->ctrl_bits[btn_pr]) == false &&
+ button_is_hidden(btnm->ctrl_bits[btn_pr]) == false) {
+ btnm->btn_id_sel = btn_pr;
+ invalidate_button_area(obj, btnm->btn_id_sel); /*Invalidate the new area*/
+ }
+ }
+ }
+
+ if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
+ if(button_is_click_trig(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
+ button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
+ button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
+ button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
+ uint32_t b = btnm->btn_id_sel;
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
+ if(res != LV_RES_OK) return;
+ }
+ }
+ }
+ else if(code == LV_EVENT_PRESSING) {
+ void * param = lv_event_get_param(e);
+ uint16_t btn_pr = LV_BTNMATRIX_BTN_NONE;
+ /*Search the pressed area*/
+ lv_indev_t * indev = lv_indev_get_act();
+ lv_indev_type_t indev_type = lv_indev_get_type(indev);
+ if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) return;
+
+ lv_indev_get_point(indev, &p);
+ btn_pr = get_button_from_point(obj, &p);
+ /*Invalidate to old and the new areas*/
+ if(btn_pr != btnm->btn_id_sel) {
+ if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
+ invalidate_button_area(obj, btnm->btn_id_sel);
+ }
+
+ btnm->btn_id_sel = btn_pr;
+
+ lv_indev_reset_long_press(param); /*Start the log press time again on the new button*/
+ if(btn_pr != LV_BTNMATRIX_BTN_NONE &&
+ button_is_inactive(btnm->ctrl_bits[btn_pr]) == false &&
+ button_is_hidden(btnm->ctrl_bits[btn_pr]) == false) {
+ invalidate_button_area(obj, btn_pr);
+ /*Send VALUE_CHANGED for the newly pressed button*/
+ if(button_is_click_trig(btnm->ctrl_bits[btn_pr]) == false &&
+ button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
+ uint32_t b = btn_pr;
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
+ if(res != LV_RES_OK) return;
+ }
+ }
+ }
+ }
+ else if(code == LV_EVENT_RELEASED) {
+ if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
+ /*Toggle the button if enabled*/
+ if(button_is_checkable(btnm->ctrl_bits[btnm->btn_id_sel]) &&
+ !button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
+ if(button_get_checked(btnm->ctrl_bits[btnm->btn_id_sel]) && !btnm->one_check) {
+ btnm->ctrl_bits[btnm->btn_id_sel] &= (~LV_BTNMATRIX_CTRL_CHECKED);
+ }
+ else {
+ btnm->ctrl_bits[btnm->btn_id_sel] |= LV_BTNMATRIX_CTRL_CHECKED;
+ }
+ if(btnm->one_check) make_one_button_checked(obj, btnm->btn_id_sel);
+ }
+
+
+ if((button_is_click_trig(btnm->ctrl_bits[btnm->btn_id_sel]) == true ||
+ button_is_popover(btnm->ctrl_bits[btnm->btn_id_sel]) == true) &&
+ button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
+ button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
+ uint32_t b = btnm->btn_id_sel;
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
+ if(res != LV_RES_OK) return;
+ }
+ }
+
+ /*Invalidate to old pressed area*/;
+ invalidate_button_area(obj, btnm->btn_id_sel);
+
+ }
+ else if(code == LV_EVENT_LONG_PRESSED_REPEAT) {
+ if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) {
+ if(button_is_repeat_disabled(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
+ button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel]) == false &&
+ button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) == false) {
+ uint32_t b = btnm->btn_id_sel;
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &b);
+ if(res != LV_RES_OK) return;
+ }
+ }
+ }
+ else if(code == LV_EVENT_PRESS_LOST) {
+ invalidate_button_area(obj, btnm->btn_id_sel);
+ btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
+ }
+ else if(code == LV_EVENT_FOCUSED) {
+ lv_indev_t * indev = lv_event_get_param(e);
+ lv_indev_type_t indev_type = lv_indev_get_type(indev);
+
+ /*If not focused by an input device assume the last input device*/
+ if(indev == NULL) {
+ indev = lv_indev_get_next(NULL);
+ indev_type = lv_indev_get_type(indev);
+ }
+
+ bool editing = lv_group_get_editing(lv_obj_get_group(obj));
+ /*Focus the first button if there is not selected button*/
+ if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
+ if(indev_type == LV_INDEV_TYPE_KEYPAD || (indev_type == LV_INDEV_TYPE_ENCODER && editing)) {
+ uint32_t b = 0;
+ if(btnm->one_check) {
+ while(button_is_hidden(btnm->ctrl_bits[b]) || button_is_inactive(btnm->ctrl_bits[b]) ||
+ button_is_checked(btnm->ctrl_bits[b]) == false) b++;
+ }
+ else {
+ while(button_is_hidden(btnm->ctrl_bits[b]) || button_is_inactive(btnm->ctrl_bits[b])) b++;
+ }
+
+ btnm->btn_id_sel = b;
+ }
+ else {
+ btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
+ }
+ }
+ }
+ else if(code == LV_EVENT_DEFOCUSED || code == LV_EVENT_LEAVE) {
+ if(btnm->btn_id_sel != LV_BTNMATRIX_BTN_NONE) invalidate_button_area(obj, btnm->btn_id_sel);
+ btnm->btn_id_sel = LV_BTNMATRIX_BTN_NONE;
+ }
+ else if(code == LV_EVENT_KEY) {
+
+ invalidate_button_area(obj, btnm->btn_id_sel);
+
+ char c = *((char *)lv_event_get_param(e));
+ if(c == LV_KEY_RIGHT) {
+ if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) btnm->btn_id_sel = 0;
+ else btnm->btn_id_sel++;
+ if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
+
+ while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
+ btnm->btn_id_sel++;
+ if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
+ }
+ }
+ else if(c == LV_KEY_LEFT) {
+ if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) btnm->btn_id_sel = 0;
+
+ if(btnm->btn_id_sel == 0) btnm->btn_id_sel = btnm->btn_cnt - 1;
+ else if(btnm->btn_id_sel > 0) btnm->btn_id_sel--;
+
+ while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
+ if(btnm->btn_id_sel > 0) btnm->btn_id_sel--;
+ else btnm->btn_id_sel = btnm->btn_cnt - 1;
+ }
+ }
+ else if(c == LV_KEY_DOWN) {
+ lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
+ /*Find the area below the current*/
+ if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
+ btnm->btn_id_sel = 0;
+ while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
+ btnm->btn_id_sel++;
+ if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
+ }
+ }
+ else {
+ uint16_t area_below;
+ lv_coord_t pr_center =
+ btnm->button_areas[btnm->btn_id_sel].x1 + (lv_area_get_width(&btnm->button_areas[btnm->btn_id_sel]) >> 1);
+
+ for(area_below = btnm->btn_id_sel; area_below < btnm->btn_cnt; area_below++) {
+ if(btnm->button_areas[area_below].y1 > btnm->button_areas[btnm->btn_id_sel].y1 &&
+ pr_center >= btnm->button_areas[area_below].x1 &&
+ pr_center <= btnm->button_areas[area_below].x2 + col_gap &&
+ button_is_inactive(btnm->ctrl_bits[area_below]) == false &&
+ button_is_hidden(btnm->ctrl_bits[area_below]) == false) {
+ break;
+ }
+ }
+
+ if(area_below < btnm->btn_cnt) btnm->btn_id_sel = area_below;
+ }
+ }
+ else if(c == LV_KEY_UP) {
+ lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
+ /*Find the area below the current*/
+ if(btnm->btn_id_sel == LV_BTNMATRIX_BTN_NONE) {
+ btnm->btn_id_sel = 0;
+ while(button_is_hidden(btnm->ctrl_bits[btnm->btn_id_sel]) || button_is_inactive(btnm->ctrl_bits[btnm->btn_id_sel])) {
+ btnm->btn_id_sel++;
+ if(btnm->btn_id_sel >= btnm->btn_cnt) btnm->btn_id_sel = 0;
+ }
+ }
+ else {
+ int16_t area_above;
+ lv_coord_t pr_center =
+ btnm->button_areas[btnm->btn_id_sel].x1 + (lv_area_get_width(&btnm->button_areas[btnm->btn_id_sel]) >> 1);
+
+ for(area_above = btnm->btn_id_sel; area_above >= 0; area_above--) {
+ if(btnm->button_areas[area_above].y1 < btnm->button_areas[btnm->btn_id_sel].y1 &&
+ pr_center >= btnm->button_areas[area_above].x1 - col_gap &&
+ pr_center <= btnm->button_areas[area_above].x2 &&
+ button_is_inactive(btnm->ctrl_bits[area_above]) == false &&
+ button_is_hidden(btnm->ctrl_bits[area_above]) == false) {
+ break;
+ }
+ }
+ if(area_above >= 0) btnm->btn_id_sel = area_above;
+ }
+ }
+
+ invalidate_button_area(obj, btnm->btn_id_sel);
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ draw_main(e);
+ }
+
+}
+
+static void draw_main(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+ if(btnm->btn_cnt == 0) return;
+
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+ obj->skip_trans = 1;
+
+ lv_area_t area_obj;
+ lv_obj_get_coords(obj, &area_obj);
+
+ lv_area_t btn_area;
+
+ uint16_t btn_i = 0;
+ uint16_t txt_i = 0;
+
+ lv_draw_rect_dsc_t draw_rect_dsc_act;
+ lv_draw_label_dsc_t draw_label_dsc_act;
+
+ lv_draw_rect_dsc_t draw_rect_dsc_def;
+ lv_draw_label_dsc_t draw_label_dsc_def;
+
+ lv_state_t state_ori = obj->state;
+ obj->state = LV_STATE_DEFAULT;
+ obj->skip_trans = 1;
+ lv_draw_rect_dsc_init(&draw_rect_dsc_def);
+ lv_draw_label_dsc_init(&draw_label_dsc_def);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &draw_rect_dsc_def);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &draw_label_dsc_def);
+ obj->skip_trans = 0;
+ obj->state = state_ori;
+
+ lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t pbottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
+ lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ const size_t txt_ap_size = 256 ;
+ char * txt_ap = lv_mem_buf_get(txt_ap_size);
+#endif
+
+ 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_BTNMATRIX_DRAW_PART_BTN;
+ part_draw_dsc.rect_dsc = &draw_rect_dsc_act;
+ part_draw_dsc.label_dsc = &draw_label_dsc_act;
+
+ for(btn_i = 0; btn_i < btnm->btn_cnt; btn_i++, txt_i++) {
+ /*Search the next valid text in the map*/
+ while(strcmp(btnm->map_p[txt_i], "\n") == 0) {
+ txt_i++;
+ }
+
+ /*Skip hidden buttons*/
+ if(button_is_hidden(btnm->ctrl_bits[btn_i])) continue;
+
+ /*Get the state of the button*/
+ lv_state_t btn_state = LV_STATE_DEFAULT;
+ if(button_get_checked(btnm->ctrl_bits[btn_i])) btn_state |= LV_STATE_CHECKED;
+
+ if(button_is_inactive(btnm->ctrl_bits[btn_i])) btn_state |= LV_STATE_DISABLED;
+ else if(btn_i == btnm->btn_id_sel) {
+ if(state_ori & LV_STATE_PRESSED) btn_state |= LV_STATE_PRESSED;
+ if(state_ori & LV_STATE_FOCUSED) btn_state |= LV_STATE_FOCUSED;
+ if(state_ori & LV_STATE_FOCUS_KEY) btn_state |= LV_STATE_FOCUS_KEY;
+ if(state_ori & LV_STATE_EDITED) btn_state |= LV_STATE_EDITED;
+ }
+
+ /*Get the button's area*/
+ lv_area_copy(&btn_area, &btnm->button_areas[btn_i]);
+ btn_area.x1 += area_obj.x1;
+ btn_area.y1 += area_obj.y1;
+ btn_area.x2 += area_obj.x1;
+ btn_area.y2 += area_obj.y1;
+
+ /*Set up the draw descriptors*/
+ if(btn_state == LV_STATE_DEFAULT) {
+ lv_memcpy(&draw_rect_dsc_act, &draw_rect_dsc_def, sizeof(lv_draw_rect_dsc_t));
+ lv_memcpy(&draw_label_dsc_act, &draw_label_dsc_def, sizeof(lv_draw_label_dsc_t));
+ }
+ /*In other cases get the styles directly without caching them*/
+ else {
+ obj->state = btn_state;
+ obj->skip_trans = 1;
+ lv_draw_rect_dsc_init(&draw_rect_dsc_act);
+ lv_draw_label_dsc_init(&draw_label_dsc_act);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &draw_rect_dsc_act);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &draw_label_dsc_act);
+ obj->state = state_ori;
+ obj->skip_trans = 0;
+ }
+
+ bool recolor = button_is_recolor(btnm->ctrl_bits[btn_i]);
+ if(recolor) draw_label_dsc_act.flag |= LV_TEXT_FLAG_RECOLOR;
+ else draw_label_dsc_act.flag &= ~LV_TEXT_FLAG_RECOLOR;
+
+
+ part_draw_dsc.draw_area = &btn_area;
+ part_draw_dsc.id = btn_i;
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+
+ /*Remove borders on the edges if `LV_BORDER_SIDE_INTERNAL`*/
+ if(draw_rect_dsc_act.border_side & LV_BORDER_SIDE_INTERNAL) {
+ draw_rect_dsc_act.border_side = LV_BORDER_SIDE_FULL;
+ if(btn_area.x1 == obj->coords.x1 + pleft) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_LEFT;
+ if(btn_area.x2 == obj->coords.x2 - pright) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_RIGHT;
+ if(btn_area.y1 == obj->coords.y1 + ptop) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_TOP;
+ if(btn_area.y2 == obj->coords.y2 - pbottom) draw_rect_dsc_act.border_side &= ~LV_BORDER_SIDE_BOTTOM;
+ }
+
+ lv_coord_t btn_height = lv_area_get_height(&btn_area);
+
+ if((btn_state & LV_STATE_PRESSED) && (btnm->ctrl_bits[btn_i] & LV_BTNMATRIX_CTRL_POPOVER)) {
+ /*Push up the upper boundary of the btn area to create the popover*/
+ btn_area.y1 -= btn_height;
+ }
+
+ /*Draw the background*/
+ lv_draw_rect(draw_ctx, &draw_rect_dsc_act, &btn_area);
+
+ /*Calculate the size of the text*/
+ const lv_font_t * font = draw_label_dsc_act.font;
+ lv_coord_t letter_space = draw_label_dsc_act.letter_space;
+ lv_coord_t line_space = draw_label_dsc_act.line_space;
+ const char * txt = btnm->map_p[txt_i];
+
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ /*Get the size of the Arabic text and process it*/
+ size_t len_ap = _lv_txt_ap_calc_bytes_cnt(txt);
+ if(len_ap < txt_ap_size) {
+ _lv_txt_ap_proc(txt, txt_ap);
+ txt = txt_ap;
+ }
+#endif
+ lv_point_t txt_size;
+ lv_txt_get_size(&txt_size, txt, font, letter_space,
+ line_space, lv_area_get_width(&area_obj), draw_label_dsc_act.flag);
+
+ btn_area.x1 += (lv_area_get_width(&btn_area) - txt_size.x) / 2;
+ btn_area.y1 += (lv_area_get_height(&btn_area) - txt_size.y) / 2;
+ btn_area.x2 = btn_area.x1 + txt_size.x;
+ btn_area.y2 = btn_area.y1 + txt_size.y;
+
+ if((btn_state & LV_STATE_PRESSED) && (btnm->ctrl_bits[btn_i] & LV_BTNMATRIX_CTRL_POPOVER)) {
+ /*Push up the button text into the popover*/
+ btn_area.y1 -= btn_height / 2;
+ btn_area.y2 -= btn_height / 2;
+ }
+
+ /*Draw the text*/
+ lv_draw_label(draw_ctx, &draw_label_dsc_act, &btn_area, txt, NULL);
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+ }
+
+ obj->skip_trans = 0;
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ lv_mem_buf_release(txt_ap);
+#endif
+}
+/**
+ * Create the required number of buttons and control bytes according to a map
+ * @param obj pointer to button matrix object
+ * @param map_p pointer to a string array
+ */
+static void allocate_btn_areas_and_controls(const lv_obj_t * obj, const char ** map)
+{
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+ btnm->row_cnt = 1;
+ /*Count the buttons in the map*/
+ uint16_t btn_cnt = 0;
+ uint16_t i = 0;
+ while(map[i] && map[i][0] != '\0') {
+ if(strcmp(map[i], "\n") != 0) { /*Do not count line breaks*/
+ btn_cnt++;
+ }
+ else {
+ btnm->row_cnt++;
+ }
+ i++;
+ }
+
+ /*Do not allocate memory for the same amount of buttons*/
+ if(btn_cnt == btnm->btn_cnt) return;
+
+ if(btnm->button_areas != NULL) {
+ lv_mem_free(btnm->button_areas);
+ btnm->button_areas = NULL;
+ }
+ if(btnm->ctrl_bits != NULL) {
+ lv_mem_free(btnm->ctrl_bits);
+ btnm->ctrl_bits = NULL;
+ }
+
+ btnm->button_areas = lv_mem_alloc(sizeof(lv_area_t) * btn_cnt);
+ LV_ASSERT_MALLOC(btnm->button_areas);
+ btnm->ctrl_bits = lv_mem_alloc(sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
+ LV_ASSERT_MALLOC(btnm->ctrl_bits);
+ if(btnm->button_areas == NULL || btnm->ctrl_bits == NULL) btn_cnt = 0;
+
+ lv_memset_00(btnm->ctrl_bits, sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
+
+ btnm->btn_cnt = btn_cnt;
+}
+
+/**
+ * Get the width of a button in units (default is 1).
+ * @param ctrl_bits least significant 3 bits used (1..7 valid values)
+ * @return the width of the button in units
+ */
+static uint8_t get_button_width(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ uint8_t w = ctrl_bits & LV_BTNMATRIX_WIDTH_MASK;
+ return w != 0 ? w : 1;
+}
+
+static bool button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ return (ctrl_bits & LV_BTNMATRIX_CTRL_HIDDEN) ? true : false;
+}
+
+static bool button_is_checked(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKED) ? true : false;
+}
+
+static bool button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ return (ctrl_bits & LV_BTNMATRIX_CTRL_NO_REPEAT) ? true : false;
+}
+
+static bool button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ return (ctrl_bits & LV_BTNMATRIX_CTRL_DISABLED) ? true : false;
+}
+
+static bool button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ return (ctrl_bits & LV_BTNMATRIX_CTRL_CLICK_TRIG) ? true : false;
+}
+
+static bool button_is_popover(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ return (ctrl_bits & LV_BTNMATRIX_CTRL_POPOVER) ? true : false;
+}
+
+static bool button_is_checkable(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKABLE) ? true : false;
+}
+
+static bool button_get_checked(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKED) ? true : false;
+}
+
+static bool button_is_recolor(lv_btnmatrix_ctrl_t ctrl_bits)
+{
+ return (ctrl_bits & LV_BTNMATRIX_CTRL_RECOLOR) ? true : false;
+}
+/**
+ * Gives the button id of a button under a given point
+ * @param obj pointer to a button matrix object
+ * @param p a point with absolute coordinates
+ * @return the id of the button or LV_BTNMATRIX_BTN_NONE.
+ */
+static uint16_t get_button_from_point(lv_obj_t * obj, lv_point_t * p)
+{
+ lv_area_t obj_cords;
+ lv_area_t btn_area;
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+ uint16_t i;
+ lv_obj_get_coords(obj, &obj_cords);
+
+ lv_coord_t w = lv_obj_get_width(obj);
+ lv_coord_t h = lv_obj_get_height(obj);
+ lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+ lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t pbottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
+ lv_coord_t prow = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
+ lv_coord_t pcol = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
+
+ /*Get the half gap. Button look larger with this value. (+1 for rounding error)*/
+ prow = (prow / 2) + 1 + (prow & 1);
+ pcol = (pcol / 2) + 1 + (pcol & 1);
+
+ prow = LV_MIN(prow, BTN_EXTRA_CLICK_AREA_MAX);
+ pcol = LV_MIN(pcol, BTN_EXTRA_CLICK_AREA_MAX);
+ pright = LV_MIN(pright, BTN_EXTRA_CLICK_AREA_MAX);
+ ptop = LV_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
+ pbottom = LV_MIN(pbottom, BTN_EXTRA_CLICK_AREA_MAX);
+
+ for(i = 0; i < btnm->btn_cnt; i++) {
+ lv_area_copy(&btn_area, &btnm->button_areas[i]);
+ if(btn_area.x1 <= pleft) btn_area.x1 += obj_cords.x1 - LV_MIN(pleft, BTN_EXTRA_CLICK_AREA_MAX);
+ else btn_area.x1 += obj_cords.x1 - pcol;
+
+ if(btn_area.y1 <= ptop) btn_area.y1 += obj_cords.y1 - LV_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
+ else btn_area.y1 += obj_cords.y1 - prow;
+
+ if(btn_area.x2 >= w - pright - 2) btn_area.x2 += obj_cords.x1 + LV_MIN(pright,
+ BTN_EXTRA_CLICK_AREA_MAX); /*-2 for rounding error*/
+ else btn_area.x2 += obj_cords.x1 + pcol;
+
+ if(btn_area.y2 >= h - pbottom - 2) btn_area.y2 += obj_cords.y1 + LV_MIN(pbottom,
+ BTN_EXTRA_CLICK_AREA_MAX); /*-2 for rounding error*/
+ else btn_area.y2 += obj_cords.y1 + prow;
+
+ if(_lv_area_is_point_on(&btn_area, p, 0) != false) {
+ break;
+ }
+ }
+
+ if(i == btnm->btn_cnt) i = LV_BTNMATRIX_BTN_NONE;
+
+ return i;
+}
+
+static void invalidate_button_area(const lv_obj_t * obj, uint16_t btn_idx)
+{
+ if(btn_idx == LV_BTNMATRIX_BTN_NONE) return;
+
+ lv_area_t btn_area;
+ lv_area_t obj_area;
+
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;;
+ if(btn_idx >= btnm->btn_cnt) return;
+
+ lv_area_copy(&btn_area, &btnm->button_areas[btn_idx]);
+ lv_obj_get_coords(obj, &obj_area);
+
+ /*The buttons might have outline and shadow so make the invalidation larger with the gaps between the buttons.
+ *It assumes that the outline or shadow is smaller than the gaps*/
+ lv_coord_t row_gap = lv_obj_get_style_pad_row(obj, LV_PART_MAIN);
+ lv_coord_t col_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
+
+ /*Be sure to have a minimal extra space if row/col_gap is small*/
+ lv_coord_t dpi = lv_disp_get_dpi(lv_obj_get_disp(obj));
+ row_gap = LV_MAX(row_gap, dpi / 10);
+ col_gap = LV_MAX(col_gap, dpi / 10);
+
+ /*Convert relative coordinates to absolute*/
+ btn_area.x1 += obj_area.x1 - row_gap;
+ btn_area.y1 += obj_area.y1 - col_gap;
+ btn_area.x2 += obj_area.x1 + row_gap;
+ btn_area.y2 += obj_area.y1 + col_gap;
+
+ if((btn_idx == btnm->btn_id_sel) && (btnm->ctrl_bits[btn_idx] & LV_BTNMATRIX_CTRL_POPOVER)) {
+ /*Push up the upper boundary of the btn area to also invalidate the popover*/
+ btn_area.y1 -= lv_area_get_height(&btn_area);
+ }
+
+ lv_obj_invalidate_area(obj, &btn_area);
+}
+
+/**
+ * Enforces a single button being toggled on the button matrix.
+ * It simply clears the toggle flag on other buttons.
+ * @param obj Button matrix object
+ * @param btn_idx Button that should remain toggled
+ */
+static void make_one_button_checked(lv_obj_t * obj, uint16_t btn_idx)
+{
+ /*Save whether the button was toggled*/
+ bool was_toggled = lv_btnmatrix_has_btn_ctrl(obj, btn_idx, LV_BTNMATRIX_CTRL_CHECKED);
+
+ lv_btnmatrix_clear_btn_ctrl_all(obj, LV_BTNMATRIX_CTRL_CHECKED);
+
+ if(was_toggled) lv_btnmatrix_set_btn_ctrl(obj, btn_idx, LV_BTNMATRIX_CTRL_CHECKED);
+}
+
+/**
+ * Check if any of the buttons in the first row has the LV_BTNMATRIX_CTRL_POPOVER control flag set.
+ * @param obj Button matrix object
+ * @return true if at least one button has the flag, false otherwise
+ */
+static bool has_popovers_in_top_row(lv_obj_t * obj)
+{
+ lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
+
+ if(btnm->row_cnt <= 0) {
+ return false;
+ }
+
+ const char ** map_row = btnm->map_p;
+ uint16_t btn_cnt = 0;
+
+ while(map_row[btn_cnt] && strcmp(map_row[btn_cnt], "\n") != 0 && map_row[btn_cnt][0] != '\0') {
+ if(button_is_popover(btnm->ctrl_bits[btn_cnt])) {
+ return true;
+ }
+ btn_cnt++;
+ }
+
+ return false;
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_btnmatrix.h b/lib/lvgl/src/widgets/lv_btnmatrix.h
new file mode 100644
index 00000000..780d57b6
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_btnmatrix.h
@@ -0,0 +1,225 @@
+/**
+ * @file lv_btnmatrix.h
+ *
+ */
+
+#ifndef LV_BTNMATRIX_H
+#define LV_BTNMATRIX_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_BTNMATRIX != 0
+
+#include "../core/lv_obj.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define LV_BTNMATRIX_BTN_NONE 0xFFFF
+LV_EXPORT_CONST_INT(LV_BTNMATRIX_BTN_NONE);
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/** Type to store button control bits (disabled, hidden etc.)
+ * The first 3 bits are used to store the width*/
+enum {
+ _LV_BTNMATRIX_WIDTH = 0x0007, /**< Reserved to stire the size units*/
+ LV_BTNMATRIX_CTRL_HIDDEN = 0x0008, /**< Button hidden*/
+ LV_BTNMATRIX_CTRL_NO_REPEAT = 0x0010, /**< Do not repeat press this button.*/
+ LV_BTNMATRIX_CTRL_DISABLED = 0x0020, /**< Disable this button.*/
+ LV_BTNMATRIX_CTRL_CHECKABLE = 0x0040, /**< The button can be toggled.*/
+ LV_BTNMATRIX_CTRL_CHECKED = 0x0080, /**< Button is currently toggled (e.g. checked).*/
+ LV_BTNMATRIX_CTRL_CLICK_TRIG = 0x0100, /**< 1: Send LV_EVENT_VALUE_CHANGE on CLICK, 0: Send LV_EVENT_VALUE_CHANGE on PRESS*/
+ LV_BTNMATRIX_CTRL_POPOVER = 0x0200, /**< Show a popover when pressing this key*/
+ LV_BTNMATRIX_CTRL_RECOLOR = 0x1000, /**< Enable text recoloring with `#color`*/
+ _LV_BTNMATRIX_CTRL_RESERVED = 0x2000, /**< Reserved for later use*/
+ LV_BTNMATRIX_CTRL_CUSTOM_1 = 0x4000, /**< Custom free to use flag*/
+ LV_BTNMATRIX_CTRL_CUSTOM_2 = 0x8000, /**< Custom free to use flag*/
+};
+
+typedef uint16_t lv_btnmatrix_ctrl_t;
+
+typedef bool (*lv_btnmatrix_btn_draw_cb_t)(lv_obj_t * btnm, uint32_t btn_id, const lv_area_t * draw_area,
+ const lv_area_t * clip_area);
+
+/*Data of button matrix*/
+typedef struct {
+ lv_obj_t obj;
+ const char ** map_p; /*Pointer to the current map*/
+ lv_area_t * button_areas; /*Array of areas of buttons*/
+ lv_btnmatrix_ctrl_t * ctrl_bits; /*Array of control bytes*/
+ uint16_t btn_cnt; /*Number of button in 'map_p'(Handled by the library)*/
+ uint16_t row_cnt; /*Number of rows in 'map_p'(Handled by the library)*/
+ uint16_t btn_id_sel; /*Index of the active button (being pressed/released etc) or LV_BTNMATRIX_BTN_NONE*/
+ uint8_t one_check : 1; /*Single button toggled at once*/
+} lv_btnmatrix_t;
+
+extern const lv_obj_class_t lv_btnmatrix_class;
+
+/**
+ * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_btnmatrix_class`
+ * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END`
+ */
+typedef enum {
+ LV_BTNMATRIX_DRAW_PART_BTN, /**< The rectangle and label of buttons*/
+} lv_btnmatrix_draw_part_type_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a button matrix object
+ * @param parent pointer to an object, it will be the parent of the new button matrix
+ * @return pointer to the created button matrix
+ */
+lv_obj_t * lv_btnmatrix_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set a new map. Buttons will be created/deleted according to the map. The
+ * button matrix keeps a reference to the map and so the string array must not
+ * be deallocated during the life of the matrix.
+ * @param obj pointer to a button matrix object
+ * @param map pointer a string array. The last string has to be: "". Use "\n" to make a line break.
+ */
+void lv_btnmatrix_set_map(lv_obj_t * obj, const char * map[]);
+
+/**
+ * Set the button control map (hidden, disabled etc.) for a button matrix.
+ * The control map array will be copied and so may be deallocated after this
+ * function returns.
+ * @param obj pointer to a button matrix object
+ * @param ctrl_map pointer to an array of `lv_btn_ctrl_t` control bytes. The
+ * length of the array and position of the elements must match
+ * the number and order of the individual buttons (i.e. excludes
+ * newline entries).
+ * An element of the map should look like e.g.:
+ * `ctrl_map[0] = width | LV_BTNMATRIX_CTRL_NO_REPEAT | LV_BTNMATRIX_CTRL_TGL_ENABLE`
+ */
+void lv_btnmatrix_set_ctrl_map(lv_obj_t * obj, const lv_btnmatrix_ctrl_t ctrl_map[]);
+
+/**
+ * Set the selected buttons
+ * @param obj pointer to button matrix object
+ * @param btn_id 0 based index of the button to modify. (Not counting new lines)
+ */
+void lv_btnmatrix_set_selected_btn(lv_obj_t * obj, uint16_t btn_id);
+
+/**
+ * Set the attributes of a button of the button matrix
+ * @param obj pointer to button matrix object
+ * @param btn_id 0 based index of the button to modify. (Not counting new lines)
+ * @param ctrl OR-ed attributs. E.g. `LV_BTNMATRIX_CTRL_NO_REPEAT | LV_BTNMATRIX_CTRL_CHECKABLE`
+ */
+void lv_btnmatrix_set_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl);
+
+/**
+ * Clear the attributes of a button of the button matrix
+ * @param obj pointer to button matrix object
+ * @param btn_id 0 based index of the button to modify. (Not counting new lines)
+ * @param ctrl OR-ed attributs. E.g. `LV_BTNMATRIX_CTRL_NO_REPEAT | LV_BTNMATRIX_CTRL_CHECKABLE`
+ */
+void lv_btnmatrix_clear_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl);
+
+/**
+ * Set attributes of all buttons of a button matrix
+ * @param obj pointer to a button matrix object
+ * @param ctrl attribute(s) to set from `lv_btnmatrix_ctrl_t`. Values can be ORed.
+ */
+void lv_btnmatrix_set_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl);
+
+/**
+ * Clear the attributes of all buttons of a button matrix
+ * @param obj pointer to a button matrix object
+ * @param ctrl attribute(s) to set from `lv_btnmatrix_ctrl_t`. Values can be ORed.
+ * @param en true: set the attributes; false: clear the attributes
+ */
+void lv_btnmatrix_clear_btn_ctrl_all(lv_obj_t * obj, lv_btnmatrix_ctrl_t ctrl);
+
+/**
+ * Set a single button's relative width.
+ * This method will cause the matrix be regenerated and is a relatively
+ * expensive operation. It is recommended that initial width be specified using
+ * `lv_btnmatrix_set_ctrl_map` and this method only be used for dynamic changes.
+ * @param obj pointer to button matrix object
+ * @param btn_id 0 based index of the button to modify.
+ * @param width relative width compared to the buttons in the same row. [1..7]
+ */
+void lv_btnmatrix_set_btn_width(lv_obj_t * obj, uint16_t btn_id, uint8_t width);
+
+/**
+ * Make the button matrix like a selector widget (only one button may be checked at a time).
+ * `LV_BTNMATRIX_CTRL_CHECKABLE` must be enabled on the buttons to be selected using
+ * `lv_btnmatrix_set_ctrl()` or `lv_btnmatrix_set_btn_ctrl_all()`.
+ * @param obj pointer to a button matrix object
+ * @param en whether "one check" mode is enabled
+ */
+void lv_btnmatrix_set_one_checked(lv_obj_t * obj, bool en);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the current map of a button matrix
+ * @param obj pointer to a button matrix object
+ * @return the current map
+ */
+const char ** lv_btnmatrix_get_map(const lv_obj_t * obj);
+
+/**
+ * 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)
+ */
+uint16_t lv_btnmatrix_get_selected_btn(const lv_obj_t * 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
+ */
+const char * lv_btnmatrix_get_btn_text(const lv_obj_t * obj, uint16_t btn_id);
+
+/**
+ * Get the whether a control value is enabled or disabled for button of a button matrix
+ * @param obj pointer to a button matrix object
+ * @param btn_id the index of a button not counting new line characters.
+ * @param ctrl control values to check (ORed value can be used)
+ * @return true: the control attribute is enabled false: disabled
+ */
+bool lv_btnmatrix_has_btn_ctrl(lv_obj_t * obj, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl);
+
+/**
+ * Tell whether "one check" mode is enabled or not.
+ * @param obj Button matrix object
+ * @return true: "one check" mode is enabled; false: disabled
+ */
+bool lv_btnmatrix_get_one_checked(const lv_obj_t * obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_BTNMATRIX*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_BTNMATRIX_H*/
diff --git a/lib/lvgl/src/widgets/lv_canvas.c b/lib/lvgl/src/widgets/lv_canvas.c
new file mode 100644
index 00000000..1f949272
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_canvas.c
@@ -0,0 +1,836 @@
+/**
+ * @file lv_canvas.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_canvas.h"
+#include "../misc/lv_assert.h"
+#include "../misc/lv_math.h"
+#include "../draw/lv_draw.h"
+#include "../core/lv_refr.h"
+
+#if LV_USE_CANVAS != 0
+
+#include "../draw/sw/lv_draw_sw.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_canvas_class
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_canvas_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_canvas_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void init_fake_disp(lv_obj_t * canvas, lv_disp_t * disp, lv_disp_drv_t * drv, lv_area_t * clip_area);
+static void deinit_fake_disp(lv_obj_t * canvas, lv_disp_t * disp);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_canvas_class = {
+ .constructor_cb = lv_canvas_constructor,
+ .destructor_cb = lv_canvas_destructor,
+ .instance_size = sizeof(lv_canvas_t),
+ .base_class = &lv_img_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_canvas_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
+ *====================*/
+
+void lv_canvas_set_buffer(lv_obj_t * obj, void * buf, lv_coord_t w, lv_coord_t h, lv_img_cf_t cf)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(buf);
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+
+ canvas->dsc.header.cf = cf;
+ canvas->dsc.header.w = w;
+ canvas->dsc.header.h = h;
+ canvas->dsc.data = buf;
+
+ lv_img_set_src(obj, &canvas->dsc);
+ lv_img_cache_invalidate_src(&canvas->dsc);
+}
+
+void lv_canvas_set_px_color(lv_obj_t * obj, lv_coord_t x, lv_coord_t y, lv_color_t c)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+
+ lv_img_buf_set_px_color(&canvas->dsc, x, y, c);
+ lv_obj_invalidate(obj);
+}
+
+void lv_canvas_set_px_opa(lv_obj_t * obj, lv_coord_t x, lv_coord_t y, lv_opa_t opa)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+
+ lv_img_buf_set_px_alpha(&canvas->dsc, x, y, opa);
+ lv_obj_invalidate(obj);
+}
+
+void lv_canvas_set_palette(lv_obj_t * obj, uint8_t id, lv_color_t c)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+
+ lv_img_buf_set_palette(&canvas->dsc, id, c);
+ lv_obj_invalidate(obj);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+lv_color_t lv_canvas_get_px(lv_obj_t * obj, lv_coord_t x, lv_coord_t y)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+ lv_color_t color = lv_obj_get_style_img_recolor(obj, LV_PART_MAIN);
+
+ return lv_img_buf_get_px_color(&canvas->dsc, x, y, color);
+}
+
+lv_img_dsc_t * lv_canvas_get_img(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+ return &canvas->dsc;
+}
+
+/*=====================
+ * Other functions
+ *====================*/
+
+void lv_canvas_copy_buf(lv_obj_t * obj, const void * to_copy, lv_coord_t x, lv_coord_t y, lv_coord_t w, lv_coord_t h)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(to_copy);
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+
+ if(x + w - 1 >= (lv_coord_t)canvas->dsc.header.w || y + h - 1 >= (lv_coord_t)canvas->dsc.header.h) {
+ LV_LOG_WARN("lv_canvas_copy_buf: x or y out of the canvas");
+ return;
+ }
+
+ uint32_t px_size = lv_img_cf_get_px_size(canvas->dsc.header.cf) >> 3;
+ uint32_t px = canvas->dsc.header.w * y * px_size + x * px_size;
+ uint8_t * to_copy8 = (uint8_t *)to_copy;
+ lv_coord_t i;
+ for(i = 0; i < h; i++) {
+ lv_memcpy((void *)&canvas->dsc.data[px], to_copy8, w * px_size);
+ px += canvas->dsc.header.w * px_size;
+ to_copy8 += w * px_size;
+ }
+}
+
+void lv_canvas_transform(lv_obj_t * obj, lv_img_dsc_t * src_img, int16_t angle, uint16_t zoom, lv_coord_t offset_x,
+ lv_coord_t offset_y,
+ int32_t pivot_x, int32_t pivot_y, bool antialias)
+{
+#if LV_DRAW_COMPLEX
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(src_img);
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+ lv_img_dsc_t * dest_img = &canvas->dsc;
+
+ int32_t x;
+ int32_t y;
+
+ lv_draw_img_dsc_t draw_dsc;
+ lv_draw_img_dsc_init(&draw_dsc);
+ draw_dsc.angle = angle;
+ draw_dsc.zoom = zoom;
+ draw_dsc.pivot.x = pivot_x;
+ draw_dsc.pivot.y = pivot_y;
+ draw_dsc.antialias = antialias;
+
+ lv_area_t dest_area;
+ dest_area.x1 = -offset_x;
+ dest_area.x2 = dest_area.x1 + dest_img->header.w - 1;
+ dest_area.y1 = -offset_y;
+ dest_area.y2 = -offset_y;
+
+ lv_color_t * cbuf = lv_mem_alloc(dest_img->header.w * sizeof(lv_color_t));
+ lv_opa_t * abuf = lv_mem_alloc(dest_img->header.w * sizeof(lv_opa_t));
+ for(y = 0; y < dest_img->header.h; y++) {
+ if(y + offset_y >= 0) {
+ lv_draw_sw_transform(NULL, &dest_area, src_img->data, src_img->header.w, src_img->header.h, src_img->header.w,
+ &draw_dsc, canvas->dsc.header.cf, cbuf, abuf);
+
+ for(x = 0; x < dest_img->header.w; x++) {
+ if(abuf[x]) {
+ lv_img_buf_set_px_color(dest_img, x, y, cbuf[x]);
+ lv_img_buf_set_px_alpha(dest_img, x, y, abuf[x]);
+ }
+ }
+
+ dest_area.y1++;
+ dest_area.y2++;
+ }
+ }
+ lv_mem_free(cbuf);
+ lv_mem_free(abuf);
+
+ lv_obj_invalidate(obj);
+
+#else
+ LV_UNUSED(obj);
+ LV_UNUSED(src_img);
+ LV_UNUSED(angle);
+ LV_UNUSED(zoom);
+ LV_UNUSED(offset_x);
+ LV_UNUSED(offset_y);
+ LV_UNUSED(pivot_x);
+ LV_UNUSED(pivot_y);
+ LV_UNUSED(antialias);
+ LV_LOG_WARN("Can't transform canvas with LV_DRAW_COMPLEX == 0");
+#endif
+}
+
+void lv_canvas_blur_hor(lv_obj_t * obj, const lv_area_t * area, uint16_t r)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ if(r == 0) return;
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+
+ lv_area_t a;
+ if(area) {
+ lv_area_copy(&a, area);
+ if(a.x1 < 0) a.x1 = 0;
+ if(a.y1 < 0) a.y1 = 0;
+ if(a.x2 > canvas->dsc.header.w - 1) a.x2 = canvas->dsc.header.w - 1;
+ if(a.y2 > canvas->dsc.header.h - 1) a.y2 = canvas->dsc.header.h - 1;
+ }
+ else {
+ a.x1 = 0;
+ a.y1 = 0;
+ a.x2 = canvas->dsc.header.w - 1;
+ a.y2 = canvas->dsc.header.h - 1;
+ }
+
+ lv_color_t color = lv_obj_get_style_img_recolor(obj, LV_PART_MAIN);
+
+ uint16_t r_back = r / 2;
+ uint16_t r_front = r / 2;
+
+ if((r & 0x1) == 0) r_back--;
+
+ bool has_alpha = lv_img_cf_has_alpha(canvas->dsc.header.cf);
+
+ lv_coord_t line_w = lv_img_buf_get_img_size(canvas->dsc.header.w, 1, canvas->dsc.header.cf);
+ uint8_t * line_buf = lv_mem_buf_get(line_w);
+
+ lv_img_dsc_t line_img;
+ line_img.data = line_buf;
+ line_img.header.always_zero = 0;
+ line_img.header.w = canvas->dsc.header.w;
+ line_img.header.h = 1;
+ line_img.header.cf = canvas->dsc.header.cf;
+
+ lv_coord_t x;
+ lv_coord_t y;
+ lv_coord_t x_safe;
+
+ for(y = a.y1; y <= a.y2; y++) {
+ uint32_t asum = 0;
+ uint32_t rsum = 0;
+ uint32_t gsum = 0;
+ uint32_t bsum = 0;
+
+ lv_color_t c;
+ lv_opa_t opa = LV_OPA_TRANSP;
+ lv_memcpy(line_buf, &canvas->dsc.data[y * line_w], line_w);
+
+ for(x = a.x1 - r_back; x <= a.x1 + r_front; x++) {
+ x_safe = x < 0 ? 0 : x;
+ x_safe = x_safe > canvas->dsc.header.w - 1 ? canvas->dsc.header.w - 1 : x_safe;
+
+ c = lv_img_buf_get_px_color(&line_img, x_safe, 0, color);
+ if(has_alpha) opa = lv_img_buf_get_px_alpha(&line_img, x_safe, 0);
+
+ rsum += c.ch.red;
+#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP
+ gsum += (c.ch.green_h << 3) + c.ch.green_l;
+#else
+ gsum += c.ch.green;
+#endif
+ bsum += c.ch.blue;
+ if(has_alpha) asum += opa;
+ }
+
+ /*Just to indicate that the px is visible*/
+ if(has_alpha == false) asum = LV_OPA_COVER;
+
+ for(x = a.x1; x <= a.x2; x++) {
+
+ if(asum) {
+ c.ch.red = rsum / r;
+#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP
+ uint8_t gtmp = gsum / r;
+ c.ch.green_h = gtmp >> 3;
+ c.ch.green_l = gtmp & 0x7;
+#else
+ c.ch.green = gsum / r;
+#endif
+ c.ch.blue = bsum / r;
+ if(has_alpha) opa = asum / r;
+
+ lv_img_buf_set_px_color(&canvas->dsc, x, y, c);
+ }
+ if(has_alpha) lv_img_buf_set_px_alpha(&canvas->dsc, x, y, opa);
+
+ x_safe = x - r_back;
+ x_safe = x_safe < 0 ? 0 : x_safe;
+ c = lv_img_buf_get_px_color(&line_img, x_safe, 0, color);
+ if(has_alpha) opa = lv_img_buf_get_px_alpha(&line_img, x_safe, 0);
+
+ rsum -= c.ch.red;
+#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP
+ gsum -= (c.ch.green_h << 3) + c.ch.green_l;
+#else
+ gsum -= c.ch.green;
+#endif
+ bsum -= c.ch.blue;
+ if(has_alpha) asum -= opa;
+
+ x_safe = x + 1 + r_front;
+ x_safe = x_safe > canvas->dsc.header.w - 1 ? canvas->dsc.header.w - 1 : x_safe;
+ c = lv_img_buf_get_px_color(&line_img, x_safe, 0, lv_color_white());
+ if(has_alpha) opa = lv_img_buf_get_px_alpha(&line_img, x_safe, 0);
+
+ rsum += c.ch.red;
+#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP
+ gsum += (c.ch.green_h << 3) + c.ch.green_l;
+#else
+ gsum += c.ch.green;
+#endif
+ bsum += c.ch.blue;
+ if(has_alpha) asum += opa;
+ }
+ }
+ lv_obj_invalidate(obj);
+
+ lv_mem_buf_release(line_buf);
+}
+
+void lv_canvas_blur_ver(lv_obj_t * obj, const lv_area_t * area, uint16_t r)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ if(r == 0) return;
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+
+ lv_area_t a;
+ if(area) {
+ lv_area_copy(&a, area);
+ if(a.x1 < 0) a.x1 = 0;
+ if(a.y1 < 0) a.y1 = 0;
+ if(a.x2 > canvas->dsc.header.w - 1) a.x2 = canvas->dsc.header.w - 1;
+ if(a.y2 > canvas->dsc.header.h - 1) a.y2 = canvas->dsc.header.h - 1;
+ }
+ else {
+ a.x1 = 0;
+ a.y1 = 0;
+ a.x2 = canvas->dsc.header.w - 1;
+ a.y2 = canvas->dsc.header.h - 1;
+ }
+
+ lv_color_t color = lv_obj_get_style_img_recolor(obj, LV_PART_MAIN);
+
+ uint16_t r_back = r / 2;
+ uint16_t r_front = r / 2;
+
+ if((r & 0x1) == 0) r_back--;
+
+ bool has_alpha = lv_img_cf_has_alpha(canvas->dsc.header.cf);
+ lv_coord_t col_w = lv_img_buf_get_img_size(1, canvas->dsc.header.h, canvas->dsc.header.cf);
+ uint8_t * col_buf = lv_mem_buf_get(col_w);
+ lv_img_dsc_t line_img;
+
+ line_img.data = col_buf;
+ line_img.header.always_zero = 0;
+ line_img.header.w = 1;
+ line_img.header.h = canvas->dsc.header.h;
+ line_img.header.cf = canvas->dsc.header.cf;
+
+ lv_coord_t x;
+ lv_coord_t y;
+ lv_coord_t y_safe;
+
+ for(x = a.x1; x <= a.x2; x++) {
+ uint32_t asum = 0;
+ uint32_t rsum = 0;
+ uint32_t gsum = 0;
+ uint32_t bsum = 0;
+
+ lv_color_t c;
+ lv_opa_t opa = LV_OPA_COVER;
+
+ for(y = a.y1 - r_back; y <= a.y1 + r_front; y++) {
+ y_safe = y < 0 ? 0 : y;
+ y_safe = y_safe > canvas->dsc.header.h - 1 ? canvas->dsc.header.h - 1 : y_safe;
+
+ c = lv_img_buf_get_px_color(&canvas->dsc, x, y_safe, color);
+ if(has_alpha) opa = lv_img_buf_get_px_alpha(&canvas->dsc, x, y_safe);
+
+ lv_img_buf_set_px_color(&line_img, 0, y_safe, c);
+ if(has_alpha) lv_img_buf_set_px_alpha(&line_img, 0, y_safe, opa);
+
+ rsum += c.ch.red;
+#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP
+ gsum += (c.ch.green_h << 3) + c.ch.green_l;
+#else
+ gsum += c.ch.green;
+#endif
+ bsum += c.ch.blue;
+ if(has_alpha) asum += opa;
+ }
+
+ /*Just to indicate that the px is visible*/
+ if(has_alpha == false) asum = LV_OPA_COVER;
+
+ for(y = a.y1; y <= a.y2; y++) {
+ if(asum) {
+ c.ch.red = rsum / r;
+#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP
+ uint8_t gtmp = gsum / r;
+ c.ch.green_h = gtmp >> 3;
+ c.ch.green_l = gtmp & 0x7;
+#else
+ c.ch.green = gsum / r;
+#endif
+ c.ch.blue = bsum / r;
+ if(has_alpha) opa = asum / r;
+
+ lv_img_buf_set_px_color(&canvas->dsc, x, y, c);
+ }
+ if(has_alpha) lv_img_buf_set_px_alpha(&canvas->dsc, x, y, opa);
+
+ y_safe = y - r_back;
+ y_safe = y_safe < 0 ? 0 : y_safe;
+ c = lv_img_buf_get_px_color(&line_img, 0, y_safe, color);
+ if(has_alpha) opa = lv_img_buf_get_px_alpha(&line_img, 0, y_safe);
+
+ rsum -= c.ch.red;
+#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP
+ gsum -= (c.ch.green_h << 3) + c.ch.green_l;
+#else
+ gsum -= c.ch.green;
+#endif
+ bsum -= c.ch.blue;
+ if(has_alpha) asum -= opa;
+
+ y_safe = y + 1 + r_front;
+ y_safe = y_safe > canvas->dsc.header.h - 1 ? canvas->dsc.header.h - 1 : y_safe;
+
+ c = lv_img_buf_get_px_color(&canvas->dsc, x, y_safe, color);
+ if(has_alpha) opa = lv_img_buf_get_px_alpha(&canvas->dsc, x, y_safe);
+
+ lv_img_buf_set_px_color(&line_img, 0, y_safe, c);
+ if(has_alpha) lv_img_buf_set_px_alpha(&line_img, 0, y_safe, opa);
+
+ rsum += c.ch.red;
+#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP
+ gsum += (c.ch.green_h << 3) + c.ch.green_l;
+#else
+ gsum += c.ch.green;
+#endif
+ bsum += c.ch.blue;
+ if(has_alpha) asum += opa;
+ }
+ }
+
+ lv_obj_invalidate(obj);
+
+ lv_mem_buf_release(col_buf);
+}
+
+void lv_canvas_fill_bg(lv_obj_t * canvas, lv_color_t color, lv_opa_t opa)
+{
+ LV_ASSERT_OBJ(canvas, MY_CLASS);
+
+ lv_img_dsc_t * dsc = lv_canvas_get_img(canvas);
+
+ if(dsc->header.cf == LV_IMG_CF_INDEXED_1BIT) {
+ uint32_t row_byte_cnt = (dsc->header.w + 7) >> 3;
+ /*+8 skip the palette*/
+ lv_memset((uint8_t *)dsc->data + 8, color.full ? 0xff : 0x00, row_byte_cnt * dsc->header.h);
+ }
+ else if(dsc->header.cf == LV_IMG_CF_ALPHA_1BIT) {
+ uint32_t row_byte_cnt = (dsc->header.w + 7) >> 3;
+ lv_memset((uint8_t *)dsc->data, opa > LV_OPA_50 ? 0xff : 0x00, row_byte_cnt * dsc->header.h);
+ }
+ else {
+ uint32_t x;
+ uint32_t y;
+ for(y = 0; y < dsc->header.h; y++) {
+ for(x = 0; x < dsc->header.w; x++) {
+ lv_img_buf_set_px_color(dsc, x, y, color);
+ lv_img_buf_set_px_alpha(dsc, x, y, opa);
+ }
+ }
+ }
+
+ lv_obj_invalidate(canvas);
+}
+
+void lv_canvas_draw_rect(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, lv_coord_t w, lv_coord_t h,
+ const lv_draw_rect_dsc_t * draw_dsc)
+{
+ LV_ASSERT_OBJ(canvas, MY_CLASS);
+
+ lv_img_dsc_t * dsc = lv_canvas_get_img(canvas);
+
+ if(dsc->header.cf >= LV_IMG_CF_INDEXED_1BIT && dsc->header.cf <= LV_IMG_CF_INDEXED_8BIT) {
+ LV_LOG_WARN("lv_canvas_draw_rect: can't draw to LV_IMG_CF_INDEXED canvas");
+ return;
+ }
+
+ /*Create a dummy display to fool the lv_draw function.
+ *It will think it draws to real screen.*/
+ lv_disp_t fake_disp;
+ lv_disp_drv_t driver;
+ lv_area_t clip_area;
+ init_fake_disp(canvas, &fake_disp, &driver, &clip_area);
+
+ lv_disp_t * refr_ori = _lv_refr_get_disp_refreshing();
+ _lv_refr_set_disp_refreshing(&fake_disp);
+
+ /*Disable anti-aliasing if drawing with transparent color to chroma keyed canvas*/
+ lv_color_t ctransp = LV_COLOR_CHROMA_KEY;
+ if(dsc->header.cf == LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED &&
+ draw_dsc->bg_color.full == ctransp.full) {
+ fake_disp.driver->antialiasing = 0;
+ }
+
+ lv_area_t coords;
+ coords.x1 = x;
+ coords.y1 = y;
+ coords.x2 = x + w - 1;
+ coords.y2 = y + h - 1;
+
+ lv_draw_rect(driver.draw_ctx, draw_dsc, &coords);
+
+ _lv_refr_set_disp_refreshing(refr_ori);
+
+ deinit_fake_disp(canvas, &fake_disp);
+
+ lv_obj_invalidate(canvas);
+}
+
+void lv_canvas_draw_text(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, lv_coord_t max_w,
+ lv_draw_label_dsc_t * draw_dsc, const char * txt)
+{
+ LV_ASSERT_OBJ(canvas, MY_CLASS);
+
+ lv_img_dsc_t * dsc = lv_canvas_get_img(canvas);
+
+ if(dsc->header.cf >= LV_IMG_CF_INDEXED_1BIT && dsc->header.cf <= LV_IMG_CF_INDEXED_8BIT) {
+ LV_LOG_WARN("lv_canvas_draw_text: can't draw to LV_IMG_CF_INDEXED canvas");
+ return;
+ }
+
+ /*Create a dummy display to fool the lv_draw function.
+ *It will think it draws to real screen.*/
+ lv_disp_t fake_disp;
+ lv_disp_drv_t driver;
+ lv_area_t clip_area;
+ init_fake_disp(canvas, &fake_disp, &driver, &clip_area);
+
+ lv_disp_t * refr_ori = _lv_refr_get_disp_refreshing();
+ _lv_refr_set_disp_refreshing(&fake_disp);
+
+ lv_area_t coords;
+ coords.x1 = x;
+ coords.y1 = y;
+ coords.x2 = x + max_w - 1;
+ coords.y2 = dsc->header.h - 1;
+ lv_draw_label(driver.draw_ctx, draw_dsc, &coords, txt, NULL);
+
+ _lv_refr_set_disp_refreshing(refr_ori);
+
+ deinit_fake_disp(canvas, &fake_disp);
+
+ lv_obj_invalidate(canvas);
+}
+
+void lv_canvas_draw_img(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, const void * src,
+ const lv_draw_img_dsc_t * draw_dsc)
+{
+ LV_ASSERT_OBJ(canvas, MY_CLASS);
+
+ lv_img_dsc_t * dsc = lv_canvas_get_img(canvas);
+
+ if(dsc->header.cf >= LV_IMG_CF_INDEXED_1BIT && dsc->header.cf <= LV_IMG_CF_INDEXED_8BIT) {
+ LV_LOG_WARN("lv_canvas_draw_img: can't draw to LV_IMG_CF_INDEXED canvas");
+ return;
+ }
+
+ lv_img_header_t header;
+ lv_res_t res = lv_img_decoder_get_info(src, &header);
+ if(res != LV_RES_OK) {
+ LV_LOG_WARN("lv_canvas_draw_img: Couldn't get the image data.");
+ return;
+ }
+ /*Create a dummy display to fool the lv_draw function.
+ *It will think it draws to real screen.*/
+ lv_disp_t fake_disp;
+ lv_disp_drv_t driver;
+ lv_area_t clip_area;
+ init_fake_disp(canvas, &fake_disp, &driver, &clip_area);
+
+ lv_disp_t * refr_ori = _lv_refr_get_disp_refreshing();
+ _lv_refr_set_disp_refreshing(&fake_disp);
+
+ lv_area_t coords;
+ coords.x1 = x;
+ coords.y1 = y;
+ coords.x2 = x + header.w - 1;
+ coords.y2 = y + header.h - 1;
+
+ lv_draw_img(driver.draw_ctx, draw_dsc, &coords, src);
+
+ _lv_refr_set_disp_refreshing(refr_ori);
+
+ deinit_fake_disp(canvas, &fake_disp);
+
+ lv_obj_invalidate(canvas);
+}
+
+void lv_canvas_draw_line(lv_obj_t * canvas, const lv_point_t points[], uint32_t point_cnt,
+ const lv_draw_line_dsc_t * draw_dsc)
+{
+ LV_ASSERT_OBJ(canvas, MY_CLASS);
+
+ lv_img_dsc_t * dsc = lv_canvas_get_img(canvas);
+
+ if(dsc->header.cf >= LV_IMG_CF_INDEXED_1BIT && dsc->header.cf <= LV_IMG_CF_INDEXED_8BIT) {
+ LV_LOG_WARN("lv_canvas_draw_line: can't draw to LV_IMG_CF_INDEXED canvas");
+ return;
+ }
+
+ /*Create a dummy display to fool the lv_draw function.
+ *It will think it draws to real screen.*/
+ lv_disp_t fake_disp;
+ lv_disp_drv_t driver;
+ lv_area_t clip_area;
+ init_fake_disp(canvas, &fake_disp, &driver, &clip_area);
+
+ lv_disp_t * refr_ori = _lv_refr_get_disp_refreshing();
+ _lv_refr_set_disp_refreshing(&fake_disp);
+
+
+ /*Disable anti-aliasing if drawing with transparent color to chroma keyed canvas*/
+ lv_color_t ctransp = LV_COLOR_CHROMA_KEY;
+ if(dsc->header.cf == LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED &&
+ draw_dsc->color.full == ctransp.full) {
+ fake_disp.driver->antialiasing = 0;
+ }
+
+ uint32_t i;
+ for(i = 0; i < point_cnt - 1; i++) {
+ lv_draw_line(driver.draw_ctx, draw_dsc, &points[i], &points[i + 1]);
+ }
+
+ _lv_refr_set_disp_refreshing(refr_ori);
+
+ deinit_fake_disp(canvas, &fake_disp);
+
+ lv_obj_invalidate(canvas);
+}
+
+void lv_canvas_draw_polygon(lv_obj_t * canvas, const lv_point_t points[], uint32_t point_cnt,
+ const lv_draw_rect_dsc_t * draw_dsc)
+{
+ LV_ASSERT_OBJ(canvas, MY_CLASS);
+
+ lv_img_dsc_t * dsc = lv_canvas_get_img(canvas);
+
+ if(dsc->header.cf >= LV_IMG_CF_INDEXED_1BIT && dsc->header.cf <= LV_IMG_CF_INDEXED_8BIT) {
+ LV_LOG_WARN("lv_canvas_draw_polygon: can't draw to LV_IMG_CF_INDEXED canvas");
+ return;
+ }
+
+ /*Create a dummy display to fool the lv_draw function.
+ *It will think it draws to real screen.*/
+ lv_disp_t fake_disp;
+ lv_disp_drv_t driver;
+ lv_area_t clip_area;
+ init_fake_disp(canvas, &fake_disp, &driver, &clip_area);
+
+ lv_disp_t * refr_ori = _lv_refr_get_disp_refreshing();
+ _lv_refr_set_disp_refreshing(&fake_disp);
+
+ /*Disable anti-aliasing if drawing with transparent color to chroma keyed canvas*/
+ lv_color_t ctransp = LV_COLOR_CHROMA_KEY;
+ if(dsc->header.cf == LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED &&
+ draw_dsc->bg_color.full == ctransp.full) {
+ fake_disp.driver->antialiasing = 0;
+ }
+
+ lv_draw_polygon(driver.draw_ctx, draw_dsc, points, point_cnt);
+
+ _lv_refr_set_disp_refreshing(refr_ori);
+
+ deinit_fake_disp(canvas, &fake_disp);
+
+ lv_obj_invalidate(canvas);
+}
+
+void lv_canvas_draw_arc(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, lv_coord_t r, int32_t start_angle,
+ int32_t end_angle, const lv_draw_arc_dsc_t * draw_dsc)
+{
+#if LV_DRAW_COMPLEX
+ LV_ASSERT_OBJ(canvas, MY_CLASS);
+
+ lv_img_dsc_t * dsc = lv_canvas_get_img(canvas);
+
+ if(dsc->header.cf >= LV_IMG_CF_INDEXED_1BIT && dsc->header.cf <= LV_IMG_CF_INDEXED_8BIT) {
+ LV_LOG_WARN("lv_canvas_draw_arc: can't draw to LV_IMG_CF_INDEXED canvas");
+ return;
+ }
+
+ /*Create a dummy display to fool the lv_draw function.
+ *It will think it draws to real screen.*/
+ lv_disp_t fake_disp;
+ lv_disp_drv_t driver;
+ lv_area_t clip_area;
+ init_fake_disp(canvas, &fake_disp, &driver, &clip_area);
+
+ lv_disp_t * refr_ori = _lv_refr_get_disp_refreshing();
+ _lv_refr_set_disp_refreshing(&fake_disp);
+
+ lv_point_t p = {x, y};
+ lv_draw_arc(driver.draw_ctx, draw_dsc, &p, r, start_angle, end_angle);
+
+ _lv_refr_set_disp_refreshing(refr_ori);
+
+ deinit_fake_disp(canvas, &fake_disp);
+
+ lv_obj_invalidate(canvas);
+#else
+ LV_UNUSED(canvas);
+ LV_UNUSED(x);
+ LV_UNUSED(y);
+ LV_UNUSED(r);
+ LV_UNUSED(start_angle);
+ LV_UNUSED(end_angle);
+ LV_UNUSED(draw_dsc);
+ LV_LOG_WARN("Can't draw arc with LV_DRAW_COMPLEX == 0");
+#endif
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_canvas_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+
+ canvas->dsc.header.always_zero = 0;
+ canvas->dsc.header.cf = LV_IMG_CF_TRUE_COLOR;
+ canvas->dsc.header.h = 0;
+ canvas->dsc.header.w = 0;
+ canvas->dsc.data_size = 0;
+ canvas->dsc.data = NULL;
+
+ lv_img_set_src(obj, &canvas->dsc);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_canvas_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_canvas_t * canvas = (lv_canvas_t *)obj;
+ lv_img_cache_invalidate_src(&canvas->dsc);
+}
+
+
+static void init_fake_disp(lv_obj_t * canvas, lv_disp_t * disp, lv_disp_drv_t * drv, lv_area_t * clip_area)
+{
+ lv_img_dsc_t * dsc = lv_canvas_get_img(canvas);
+
+ clip_area->x1 = 0;
+ clip_area->x2 = dsc->header.w - 1;
+ clip_area->y1 = 0;
+ clip_area->y2 = dsc->header.h - 1;
+
+ /*Allocate the fake driver on the stack as the entire display doesn't outlive this function*/
+ lv_memset_00(disp, sizeof(lv_disp_t));
+ disp->driver = drv;
+
+ lv_disp_drv_init(disp->driver);
+ disp->driver->hor_res = dsc->header.w;
+ disp->driver->ver_res = dsc->header.h;
+
+ lv_draw_ctx_t * draw_ctx = lv_mem_alloc(sizeof(lv_draw_sw_ctx_t));
+ LV_ASSERT_MALLOC(draw_ctx);
+ if(draw_ctx == NULL) return;
+ lv_draw_sw_init_ctx(drv, draw_ctx);
+ disp->driver->draw_ctx = draw_ctx;
+ draw_ctx->clip_area = clip_area;
+ draw_ctx->buf_area = clip_area;
+ draw_ctx->buf = (void *)dsc->data;
+
+ lv_disp_drv_use_generic_set_px_cb(disp->driver, dsc->header.cf);
+ if(LV_COLOR_SCREEN_TRANSP && dsc->header.cf != LV_IMG_CF_TRUE_COLOR_ALPHA) {
+ drv->screen_transp = 0;
+ }
+}
+
+static void deinit_fake_disp(lv_obj_t * canvas, lv_disp_t * disp)
+{
+ LV_UNUSED(canvas);
+ lv_draw_sw_deinit_ctx(disp->driver, disp->driver->draw_ctx);
+ lv_mem_free(disp->driver->draw_ctx);
+}
+
+
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_canvas.h b/lib/lvgl/src/widgets/lv_canvas.h
new file mode 100644
index 00000000..71f05161
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_canvas.h
@@ -0,0 +1,280 @@
+/**
+ * @file lv_canvas.h
+ *
+ */
+
+#ifndef LV_CANVAS_H
+#define LV_CANVAS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_CANVAS != 0
+
+#include "../core/lv_obj.h"
+#include "../widgets/lv_img.h"
+#include "../draw/lv_draw_img.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+extern const lv_obj_class_t lv_canvas_class;
+
+/*Data of canvas*/
+typedef struct {
+ lv_img_t img;
+ lv_img_dsc_t dsc;
+} lv_canvas_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a canvas object
+ * @param parent pointer to an object, it will be the parent of the new canvas
+ * @return pointer to the created canvas
+ */
+lv_obj_t * lv_canvas_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set a buffer for the canvas.
+ * @param buf a buffer where the content of the canvas will be.
+ * The required size is (lv_img_color_format_get_px_size(cf) * w) / 8 * h)
+ * It can be allocated with `lv_mem_alloc()` or
+ * it can be statically allocated array (e.g. static lv_color_t buf[100*50]) or
+ * it can be an address in RAM or external SRAM
+ * @param canvas pointer to a canvas object
+ * @param w width of the canvas
+ * @param h height of the canvas
+ * @param cf color format. `LV_IMG_CF_...`
+ */
+void lv_canvas_set_buffer(lv_obj_t * canvas, void * buf, lv_coord_t w, lv_coord_t h, lv_img_cf_t cf);
+
+/**
+ * Set the color of a pixel on the canvas
+ * @param canvas
+ * @param x x coordinate of the point to set
+ * @param y x coordinate of the point to set
+ * @param c color of the pixel
+ */
+void lv_canvas_set_px_color(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, lv_color_t c);
+
+/**
+ * DEPRECATED: added only for backward compatibility
+ */
+static inline void lv_canvas_set_px(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, lv_color_t c)
+{
+ lv_canvas_set_px_color(canvas, x, y, c);
+}
+
+/**
+ * Set the opacity of a pixel on the canvas
+ * @param canvas
+ * @param x x coordinate of the point to set
+ * @param y x coordinate of the point to set
+ * @param opa opacity of the pixel (0..255)
+ */
+void lv_canvas_set_px_opa(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, lv_opa_t opa);
+
+
+/**
+ * Set the palette color of a canvas with index format. Valid only for `LV_IMG_CF_INDEXED1/2/4/8`
+ * @param canvas pointer to canvas object
+ * @param id the palette color to set:
+ * - for `LV_IMG_CF_INDEXED1`: 0..1
+ * - for `LV_IMG_CF_INDEXED2`: 0..3
+ * - for `LV_IMG_CF_INDEXED4`: 0..15
+ * - for `LV_IMG_CF_INDEXED8`: 0..255
+ * @param c the color to set
+ */
+void lv_canvas_set_palette(lv_obj_t * canvas, uint8_t id, lv_color_t c);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the color of a pixel on the canvas
+ * @param canvas
+ * @param x x coordinate of the point to set
+ * @param y x coordinate of the point to set
+ * @return color of the point
+ */
+lv_color_t lv_canvas_get_px(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y);
+
+/**
+ * Get the image of the canvas as a pointer to an `lv_img_dsc_t` variable.
+ * @param canvas pointer to a canvas object
+ * @return pointer to the image descriptor.
+ */
+lv_img_dsc_t * lv_canvas_get_img(lv_obj_t * canvas);
+
+/*=====================
+ * Other functions
+ *====================*/
+
+/**
+ * Copy a buffer to the canvas
+ * @param canvas pointer to a canvas object
+ * @param to_copy buffer to copy. The color format has to match with the canvas's buffer color
+ * format
+ * @param x left side of the destination position
+ * @param y top side of the destination position
+ * @param w width of the buffer to copy
+ * @param h height of the buffer to copy
+ */
+void lv_canvas_copy_buf(lv_obj_t * canvas, const void * to_copy, lv_coord_t x, lv_coord_t y, lv_coord_t w,
+ lv_coord_t h);
+
+/**
+ * Transform and image and store the result on a canvas.
+ * @param canvas pointer to a canvas object to store the result of the transformation.
+ * @param img pointer to an image descriptor to transform.
+ * Can be the image descriptor of an other canvas too (`lv_canvas_get_img()`).
+ * @param angle the angle of rotation (0..3600), 0.1 deg resolution
+ * @param zoom zoom factor (256 no zoom);
+ * @param offset_x offset X to tell where to put the result data on destination canvas
+ * @param offset_y offset X to tell where to put the result data on destination canvas
+ * @param pivot_x pivot X of rotation. Relative to the source canvas
+ * Set to `source width / 2` to rotate around the center
+ * @param pivot_y pivot Y of rotation. Relative to the source canvas
+ * Set to `source height / 2` to rotate around the center
+ * @param antialias apply anti-aliasing during the transformation. Looks better but slower.
+ */
+void lv_canvas_transform(lv_obj_t * canvas, lv_img_dsc_t * img, int16_t angle, uint16_t zoom, lv_coord_t offset_x,
+ lv_coord_t offset_y,
+ int32_t pivot_x, int32_t pivot_y, bool antialias);
+
+/**
+ * Apply horizontal blur on the canvas
+ * @param canvas pointer to a canvas object
+ * @param area the area to blur. If `NULL` the whole canvas will be blurred.
+ * @param r radius of the blur
+ */
+void lv_canvas_blur_hor(lv_obj_t * canvas, const lv_area_t * area, uint16_t r);
+
+/**
+ * Apply vertical blur on the canvas
+ * @param canvas pointer to a canvas object
+ * @param area the area to blur. If `NULL` the whole canvas will be blurred.
+ * @param r radius of the blur
+ */
+void lv_canvas_blur_ver(lv_obj_t * canvas, const lv_area_t * area, uint16_t r);
+
+/**
+ * Fill the canvas with color
+ * @param canvas pointer to a canvas
+ * @param color the background color
+ * @param opa the desired opacity
+ */
+void lv_canvas_fill_bg(lv_obj_t * canvas, lv_color_t color, lv_opa_t opa);
+
+/**
+ * Draw a rectangle on the canvas
+ * @param canvas pointer to a canvas object
+ * @param x left coordinate of the rectangle
+ * @param y top coordinate of the rectangle
+ * @param w width of the rectangle
+ * @param h height of the rectangle
+ * @param draw_dsc descriptor of the rectangle
+ */
+void lv_canvas_draw_rect(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, lv_coord_t w, lv_coord_t h,
+ const lv_draw_rect_dsc_t * draw_dsc);
+
+/**
+ * Draw a text on the canvas.
+ * @param canvas pointer to a canvas object
+ * @param x left coordinate of the text
+ * @param y top coordinate of the text
+ * @param max_w max width of the text. The text will be wrapped to fit into this size
+ * @param draw_dsc pointer to a valid label descriptor `lv_draw_label_dsc_t`
+ * @param txt text to display
+ */
+void lv_canvas_draw_text(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, lv_coord_t max_w,
+ lv_draw_label_dsc_t * draw_dsc, const char * txt);
+
+/**
+ * Draw an image on the canvas
+ * @param canvas pointer to a canvas object
+ * @param x left coordinate of the image
+ * @param y top coordinate of the image
+ * @param src image source. Can be a pointer an `lv_img_dsc_t` variable or a path an image.
+ * @param draw_dsc pointer to a valid label descriptor `lv_draw_img_dsc_t`
+ */
+void lv_canvas_draw_img(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, const void * src,
+ const lv_draw_img_dsc_t * draw_dsc);
+
+/**
+ * Draw a line on the canvas
+ * @param canvas pointer to a canvas object
+ * @param points point of the line
+ * @param point_cnt number of points
+ * @param draw_dsc pointer to an initialized `lv_draw_line_dsc_t` variable
+ */
+void lv_canvas_draw_line(lv_obj_t * canvas, const lv_point_t points[], uint32_t point_cnt,
+ const lv_draw_line_dsc_t * draw_dsc);
+
+/**
+ * Draw a polygon on the canvas
+ * @param canvas pointer to a canvas object
+ * @param points point of the polygon
+ * @param point_cnt number of points
+ * @param draw_dsc pointer to an initialized `lv_draw_rect_dsc_t` variable
+ */
+void lv_canvas_draw_polygon(lv_obj_t * canvas, const lv_point_t points[], uint32_t point_cnt,
+ const lv_draw_rect_dsc_t * draw_dsc);
+
+/**
+ * Draw an arc on the canvas
+ * @param canvas pointer to a canvas object
+ * @param x origo x of the arc
+ * @param y origo y of the arc
+ * @param r radius of the arc
+ * @param start_angle start angle in degrees
+ * @param end_angle end angle in degrees
+ * @param draw_dsc pointer to an initialized `lv_draw_line_dsc_t` variable
+ */
+void lv_canvas_draw_arc(lv_obj_t * canvas, lv_coord_t x, lv_coord_t y, lv_coord_t r, int32_t start_angle,
+ int32_t end_angle, const lv_draw_arc_dsc_t * draw_dsc);
+
+/**********************
+ * MACROS
+ **********************/
+#define LV_CANVAS_BUF_SIZE_TRUE_COLOR(w, h) LV_IMG_BUF_SIZE_TRUE_COLOR(w, h)
+#define LV_CANVAS_BUF_SIZE_TRUE_COLOR_CHROMA_KEYED(w, h) LV_IMG_BUF_SIZE_TRUE_COLOR_CHROMA_KEYED(w, h)
+#define LV_CANVAS_BUF_SIZE_TRUE_COLOR_ALPHA(w, h) LV_IMG_BUF_SIZE_TRUE_COLOR_ALPHA(w, h)
+
+/*+ 1: to be sure no fractional row*/
+#define LV_CANVAS_BUF_SIZE_ALPHA_1BIT(w, h) LV_IMG_BUF_SIZE_ALPHA_1BIT(w, h)
+#define LV_CANVAS_BUF_SIZE_ALPHA_2BIT(w, h) LV_IMG_BUF_SIZE_ALPHA_2BIT(w, h)
+#define LV_CANVAS_BUF_SIZE_ALPHA_4BIT(w, h) LV_IMG_BUF_SIZE_ALPHA_4BIT(w, h)
+#define LV_CANVAS_BUF_SIZE_ALPHA_8BIT(w, h) LV_IMG_BUF_SIZE_ALPHA_8BIT(w, h)
+
+/*4 * X: for palette*/
+#define LV_CANVAS_BUF_SIZE_INDEXED_1BIT(w, h) LV_IMG_BUF_SIZE_INDEXED_1BIT(w, h)
+#define LV_CANVAS_BUF_SIZE_INDEXED_2BIT(w, h) LV_IMG_BUF_SIZE_INDEXED_2BIT(w, h)
+#define LV_CANVAS_BUF_SIZE_INDEXED_4BIT(w, h) LV_IMG_BUF_SIZE_INDEXED_4BIT(w, h)
+#define LV_CANVAS_BUF_SIZE_INDEXED_8BIT(w, h) LV_IMG_BUF_SIZE_INDEXED_8BIT(w, h)
+
+#endif /*LV_USE_CANVAS*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_CANVAS_H*/
diff --git a/lib/lvgl/src/widgets/lv_checkbox.c b/lib/lvgl/src/widgets/lv_checkbox.c
new file mode 100644
index 00000000..dd3b3d15
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_checkbox.c
@@ -0,0 +1,262 @@
+/**
+ * @file lv_cb.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_checkbox.h"
+#if LV_USE_CHECKBOX != 0
+
+#include "../misc/lv_assert.h"
+#include "../misc/lv_txt_ap.h"
+#include "../core/lv_group.h"
+#include "../draw/lv_draw.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_checkbox_class
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_checkbox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_checkbox_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_checkbox_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void lv_checkbox_draw(lv_event_t * e);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_checkbox_class = {
+ .constructor_cb = lv_checkbox_constructor,
+ .destructor_cb = lv_checkbox_destructor,
+ .event_cb = lv_checkbox_event,
+ .width_def = LV_SIZE_CONTENT,
+ .height_def = LV_SIZE_CONTENT,
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
+ .instance_size = sizeof(lv_checkbox_t),
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_checkbox_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
+ *====================*/
+
+void lv_checkbox_set_text(lv_obj_t * obj, const char * txt)
+{
+ lv_checkbox_t * cb = (lv_checkbox_t *)obj;
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ size_t len = _lv_txt_ap_calc_bytes_cnt(txt);
+#else
+ size_t len = strlen(txt);
+#endif
+
+ if(!cb->static_txt) cb->txt = lv_mem_realloc(cb->txt, len + 1);
+ else cb->txt = lv_mem_alloc(len + 1);
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ _lv_txt_ap_proc(txt, cb->txt);
+#else
+ strcpy(cb->txt, txt);
+#endif
+
+ cb->static_txt = 0;
+
+ lv_obj_refresh_self_size(obj);
+ lv_obj_invalidate(obj);
+}
+
+void lv_checkbox_set_text_static(lv_obj_t * obj, const char * txt)
+{
+ lv_checkbox_t * cb = (lv_checkbox_t *)obj;
+
+ if(!cb->static_txt) lv_mem_free(cb->txt);
+
+ cb->txt = (char *)txt;
+ cb->static_txt = 1;
+
+ lv_obj_refresh_self_size(obj);
+ lv_obj_invalidate(obj);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+const char * lv_checkbox_get_text(const lv_obj_t * obj)
+{
+ lv_checkbox_t * cb = (lv_checkbox_t *)obj;
+ return cb->txt;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_checkbox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_checkbox_t * cb = (lv_checkbox_t *)obj;
+
+ cb->txt = "Check box";
+ cb->static_txt = 1;
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE);
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_checkbox_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_checkbox_t * cb = (lv_checkbox_t *)obj;
+ if(!cb->static_txt) {
+ lv_mem_free(cb->txt);
+ cb->txt = NULL;
+ }
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_checkbox_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*/
+ 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_GET_SELF_SIZE) {
+ lv_point_t * p = lv_event_get_param(e);
+ lv_checkbox_t * cb = (lv_checkbox_t *)obj;
+
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
+
+ lv_point_t txt_size;
+ lv_txt_get_size(&txt_size, cb->txt, font, letter_space, line_space, LV_COORD_MAX, LV_TEXT_FLAG_NONE);
+
+ lv_coord_t bg_colp = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
+ lv_coord_t marker_leftp = lv_obj_get_style_pad_left(obj, LV_PART_INDICATOR);
+ lv_coord_t marker_rightp = lv_obj_get_style_pad_right(obj, LV_PART_INDICATOR);
+ lv_coord_t marker_topp = lv_obj_get_style_pad_top(obj, LV_PART_INDICATOR);
+ lv_coord_t marker_bottomp = lv_obj_get_style_pad_bottom(obj, LV_PART_INDICATOR);
+ lv_point_t marker_size;
+ marker_size.x = font_h + marker_leftp + marker_rightp;
+ marker_size.y = font_h + marker_topp + marker_bottomp;
+
+ p->x = marker_size.x + txt_size.x + bg_colp;
+ p->y = LV_MAX(marker_size.y, txt_size.y);
+ }
+ else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
+ lv_coord_t * s = lv_event_get_param(e);
+ lv_coord_t m = lv_obj_calculate_ext_draw_size(obj, LV_PART_INDICATOR);
+ *s = LV_MAX(*s, m);
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ lv_checkbox_draw(e);
+ }
+}
+
+static void lv_checkbox_draw(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_checkbox_t * cb = (lv_checkbox_t *)obj;
+
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+
+ lv_coord_t bg_border = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
+ lv_coord_t bg_topp = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + bg_border;
+ lv_coord_t bg_leftp = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + bg_border;
+ lv_coord_t bg_colp = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
+
+ lv_coord_t marker_leftp = lv_obj_get_style_pad_left(obj, LV_PART_INDICATOR);
+ lv_coord_t marker_rightp = lv_obj_get_style_pad_right(obj, LV_PART_INDICATOR);
+ lv_coord_t marker_topp = lv_obj_get_style_pad_top(obj, LV_PART_INDICATOR);
+ lv_coord_t marker_bottomp = lv_obj_get_style_pad_bottom(obj, LV_PART_INDICATOR);
+
+ lv_coord_t transf_w = lv_obj_get_style_transform_width(obj, LV_PART_INDICATOR);
+ lv_coord_t transf_h = lv_obj_get_style_transform_height(obj, LV_PART_INDICATOR);
+
+ lv_draw_rect_dsc_t indic_dsc;
+ lv_draw_rect_dsc_init(&indic_dsc);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &indic_dsc);
+ lv_area_t marker_area;
+ marker_area.x1 = obj->coords.x1 + bg_leftp;
+ marker_area.x2 = marker_area.x1 + font_h + marker_leftp + marker_rightp - 1;
+ marker_area.y1 = obj->coords.y1 + bg_topp;
+ marker_area.y2 = marker_area.y1 + font_h + marker_topp + marker_bottomp - 1;
+
+ lv_area_t marker_area_transf;
+ lv_area_copy(&marker_area_transf, &marker_area);
+ marker_area_transf.x1 -= transf_w;
+ marker_area_transf.x2 += transf_w;
+ marker_area_transf.y1 -= transf_h;
+ marker_area_transf.y2 += transf_h;
+
+ lv_obj_draw_part_dsc_t part_draw_dsc;
+ lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
+ part_draw_dsc.rect_dsc = &indic_dsc;
+ part_draw_dsc.class_p = MY_CLASS;
+ part_draw_dsc.type = LV_CHECKBOX_DRAW_PART_BOX;
+ part_draw_dsc.draw_area = &marker_area_transf;
+ part_draw_dsc.part = LV_PART_INDICATOR;
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+ lv_draw_rect(draw_ctx, &indic_dsc, &marker_area_transf);
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
+
+ lv_point_t txt_size;
+ lv_txt_get_size(&txt_size, cb->txt, font, letter_space, line_space, LV_COORD_MAX, LV_TEXT_FLAG_NONE);
+
+ lv_draw_label_dsc_t txt_dsc;
+ lv_draw_label_dsc_init(&txt_dsc);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_MAIN, &txt_dsc);
+
+ lv_coord_t y_ofs = (lv_area_get_height(&marker_area) - font_h) / 2;
+ lv_area_t txt_area;
+ txt_area.x1 = marker_area.x2 + bg_colp;
+ txt_area.x2 = txt_area.x1 + txt_size.x;
+ txt_area.y1 = obj->coords.y1 + bg_topp + y_ofs;
+ txt_area.y2 = txt_area.y1 + txt_size.y;
+
+ lv_draw_label(draw_ctx, &txt_dsc, &txt_area, cb->txt, NULL);
+}
+#endif
diff --git a/lib/lvgl/src/widgets/lv_checkbox.h b/lib/lvgl/src/widgets/lv_checkbox.h
new file mode 100644
index 00000000..772f500d
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_checkbox.h
@@ -0,0 +1,97 @@
+/**
+ * @file lv_cb.h
+ *
+ */
+
+#ifndef LV_CHECKBOX_H
+#define LV_CHECKBOX_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+#include "../core/lv_obj.h"
+
+#if LV_USE_CHECKBOX != 0
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ lv_obj_t obj;
+ char * txt;
+ uint32_t static_txt : 1;
+} lv_checkbox_t;
+
+extern const lv_obj_class_t lv_checkbox_class;
+
+/**
+ * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_checkbox_class`
+ * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END`
+ */
+typedef enum {
+ LV_CHECKBOX_DRAW_PART_BOX, /**< The tick box*/
+} lv_checkbox_draw_part_type_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a check box object
+ * @param parent pointer to an object, it will be the parent of the new button
+ * @return pointer to the created check box
+ */
+lv_obj_t * lv_checkbox_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set the text of a check box. `txt` will be copied and may be deallocated
+ * after this function returns.
+ * @param cb pointer to a check box
+ * @param txt the text of the check box. NULL to refresh with the current text.
+ */
+void lv_checkbox_set_text(lv_obj_t * obj, const char * txt);
+
+/**
+ * Set the text of a check box. `txt` must not be deallocated during the life
+ * of this checkbox.
+ * @param cb pointer to a check box
+ * @param txt the text of the check box.
+ */
+void lv_checkbox_set_text_static(lv_obj_t * obj, const char * txt);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the text of a check box
+ * @param cb pointer to check box object
+ * @return pointer to the text of the check box
+ */
+const char * lv_checkbox_get_text(const lv_obj_t * obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_CHECKBOX*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_CHECKBOX_H*/
diff --git a/lib/lvgl/src/widgets/lv_dropdown.c b/lib/lvgl/src/widgets/lv_dropdown.c
new file mode 100644
index 00000000..241d1777
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_dropdown.c
@@ -0,0 +1,1147 @@
+/**
+ * @file lv_dropdown.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../core/lv_obj.h"
+#include "lv_dropdown.h"
+#if LV_USE_DROPDOWN != 0
+
+#include "../misc/lv_assert.h"
+#include "../draw/lv_draw.h"
+#include "../core/lv_group.h"
+#include "../core/lv_indev.h"
+#include "../core/lv_disp.h"
+#include "../font/lv_symbol_def.h"
+#include "../misc/lv_anim.h"
+#include "../misc/lv_math.h"
+#include "../misc/lv_txt_ap.h"
+#include <string.h>
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_dropdown_class
+#define MY_CLASS_LIST &lv_dropdownlist_class
+
+#define LV_DROPDOWN_PR_NONE 0xFFFF
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static lv_obj_t * lv_dropdown_list_create(lv_obj_t * parent);
+static void lv_dropdown_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_dropdown_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void draw_main(lv_event_t * e);
+
+static void lv_dropdownlist_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_dropdownlist_destructor(const lv_obj_class_t * class_p, lv_obj_t * list_obj);
+static void lv_dropdown_list_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void draw_list(lv_event_t * e);
+
+static void draw_box(lv_obj_t * dropdown_obj, lv_draw_ctx_t * draw_ctx, uint16_t id, lv_state_t state);
+static void draw_box_label(lv_obj_t * dropdown_obj, lv_draw_ctx_t * draw_ctx, uint16_t id, lv_state_t state);
+static lv_res_t btn_release_handler(lv_obj_t * obj);
+static lv_res_t list_release_handler(lv_obj_t * list_obj);
+static void list_press_handler(lv_obj_t * page);
+static uint16_t get_id_on_point(lv_obj_t * dropdown_obj, lv_coord_t y);
+static void position_to_selected(lv_obj_t * obj);
+static lv_obj_t * get_label(const lv_obj_t * obj);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_dropdown_class = {
+ .constructor_cb = lv_dropdown_constructor,
+ .destructor_cb = lv_dropdown_destructor,
+ .event_cb = lv_dropdown_event,
+ .width_def = LV_DPI_DEF,
+ .height_def = LV_SIZE_CONTENT,
+ .instance_size = sizeof(lv_dropdown_t),
+ .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
+ .base_class = &lv_obj_class
+};
+
+const lv_obj_class_t lv_dropdownlist_class = {
+ .constructor_cb = lv_dropdownlist_constructor,
+ .destructor_cb = lv_dropdownlist_destructor,
+ .event_cb = lv_dropdown_list_event,
+ .instance_size = sizeof(lv_dropdown_list_t),
+ .base_class = &lv_obj_class
+};
+
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_dropdown_create(lv_obj_t * parent)
+{
+ LV_LOG_INFO("begin");
+ lv_obj_t * obj = lv_obj_class_create_obj(&lv_dropdown_class, parent);
+ lv_obj_class_init_obj(obj);
+ return obj;
+}
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+void lv_dropdown_set_text(lv_obj_t * obj, const char * txt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ if(dropdown->text == txt) return;
+
+ dropdown->text = txt;
+
+ lv_obj_invalidate(obj);
+}
+
+void lv_dropdown_set_options(lv_obj_t * obj, const char * options)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(options);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ /*Count the '\n'-s to determine the number of options*/
+ dropdown->option_cnt = 0;
+ uint32_t i;
+ for(i = 0; options[i] != '\0'; i++) {
+ if(options[i] == '\n') dropdown->option_cnt++;
+ }
+ dropdown->option_cnt++; /*Last option has no `\n`*/
+ dropdown->sel_opt_id = 0;
+ dropdown->sel_opt_id_orig = 0;
+
+ /*Allocate space for the new text*/
+#if LV_USE_ARABIC_PERSIAN_CHARS == 0
+ size_t len = strlen(options) + 1;
+#else
+ size_t len = _lv_txt_ap_calc_bytes_cnt(options) + 1;
+#endif
+
+ if(dropdown->options != NULL && dropdown->static_txt == 0) {
+ lv_mem_free(dropdown->options);
+ dropdown->options = NULL;
+ }
+
+ dropdown->options = lv_mem_alloc(len);
+
+ LV_ASSERT_MALLOC(dropdown->options);
+ if(dropdown->options == NULL) return;
+
+#if LV_USE_ARABIC_PERSIAN_CHARS == 0
+ strcpy(dropdown->options, options);
+#else
+ _lv_txt_ap_proc(options, dropdown->options);
+#endif
+
+ /*Now the text is dynamically allocated*/
+ dropdown->static_txt = 0;
+
+ lv_obj_invalidate(obj);
+ if(dropdown->list) lv_obj_invalidate(dropdown->list);
+}
+
+void lv_dropdown_set_options_static(lv_obj_t * obj, const char * options)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(options);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ /*Count the '\n'-s to determine the number of options*/
+ dropdown->option_cnt = 0;
+ uint32_t i;
+ for(i = 0; options[i] != '\0'; i++) {
+ if(options[i] == '\n') dropdown->option_cnt++;
+ }
+ dropdown->option_cnt++; /*Last option has no `\n`*/
+ dropdown->sel_opt_id = 0;
+ dropdown->sel_opt_id_orig = 0;
+
+ if(dropdown->static_txt == 0 && dropdown->options != NULL) {
+ lv_mem_free(dropdown->options);
+ dropdown->options = NULL;
+ }
+
+ dropdown->static_txt = 1;
+ dropdown->options = (char *)options;
+
+ lv_obj_invalidate(obj);
+ if(dropdown->list) lv_obj_invalidate(dropdown->list);
+}
+
+void lv_dropdown_add_option(lv_obj_t * obj, const char * option, uint32_t pos)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(option);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ /*Convert static options to dynamic*/
+ if(dropdown->static_txt != 0) {
+ char * static_options = dropdown->options;
+ size_t len = strlen(static_options) + 1;
+
+ dropdown->options = lv_mem_alloc(len);
+ LV_ASSERT_MALLOC(dropdown->options);
+ if(dropdown->options == NULL) return;
+
+ strcpy(dropdown->options, static_options);
+ dropdown->static_txt = 0;
+ }
+
+ /*Allocate space for the new option*/
+ size_t old_len = (dropdown->options == NULL) ? 0 : strlen(dropdown->options);
+#if LV_USE_ARABIC_PERSIAN_CHARS == 0
+ size_t ins_len = strlen(option) + 1;
+#else
+ size_t ins_len = _lv_txt_ap_calc_bytes_cnt(option) + 1;
+#endif
+
+ size_t new_len = ins_len + old_len + 2; /*+2 for terminating NULL and possible \n*/
+ dropdown->options = lv_mem_realloc(dropdown->options, new_len + 1);
+ LV_ASSERT_MALLOC(dropdown->options);
+ if(dropdown->options == NULL) return;
+
+ dropdown->options[old_len] = '\0';
+
+ /*Find the insert character position*/
+ uint32_t insert_pos = old_len;
+ if(pos != LV_DROPDOWN_POS_LAST) {
+ uint32_t opcnt = 0;
+ for(insert_pos = 0; dropdown->options[insert_pos] != 0; insert_pos++) {
+ if(opcnt == pos)
+ break;
+ if(dropdown->options[insert_pos] == '\n')
+ opcnt++;
+ }
+ }
+
+ /*Add delimiter to existing options*/
+ if((insert_pos > 0) && (pos >= dropdown->option_cnt))
+ _lv_txt_ins(dropdown->options, _lv_txt_encoded_get_char_id(dropdown->options, insert_pos++), "\n");
+
+ /*Insert the new option, adding \n if necessary*/
+ char * ins_buf = lv_mem_buf_get(ins_len + 2); /*+ 2 for terminating NULL and possible \n*/
+ LV_ASSERT_MALLOC(ins_buf);
+ if(ins_buf == NULL) return;
+#if LV_USE_ARABIC_PERSIAN_CHARS == 0
+ strcpy(ins_buf, option);
+#else
+ _lv_txt_ap_proc(option, ins_buf);
+#endif
+ if(pos < dropdown->option_cnt) strcat(ins_buf, "\n");
+
+ _lv_txt_ins(dropdown->options, _lv_txt_encoded_get_char_id(dropdown->options, insert_pos), ins_buf);
+ lv_mem_buf_release(ins_buf);
+
+ dropdown->option_cnt++;
+
+ lv_obj_invalidate(obj);
+ if(dropdown->list) lv_obj_invalidate(dropdown->list);
+}
+
+void lv_dropdown_clear_options(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ if(dropdown->options == NULL) return;
+
+ if(dropdown->static_txt == 0)
+ lv_mem_free(dropdown->options);
+
+ dropdown->options = NULL;
+ dropdown->static_txt = 0;
+ dropdown->option_cnt = 0;
+
+ lv_obj_invalidate(obj);
+ if(dropdown->list) lv_obj_invalidate(dropdown->list);
+}
+
+void lv_dropdown_set_selected(lv_obj_t * obj, uint16_t sel_opt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ if(dropdown->sel_opt_id == sel_opt) return;
+
+ dropdown->sel_opt_id = sel_opt < dropdown->option_cnt ? sel_opt : dropdown->option_cnt - 1;
+ dropdown->sel_opt_id_orig = dropdown->sel_opt_id;
+
+ lv_obj_invalidate(obj);
+}
+
+void lv_dropdown_set_dir(lv_obj_t * obj, lv_dir_t dir)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ if(dropdown->dir == dir) return;
+
+ dropdown->dir = dir;
+
+ lv_obj_invalidate(obj);
+}
+
+void lv_dropdown_set_symbol(lv_obj_t * obj, const void * symbol)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ dropdown->symbol = symbol;
+ lv_obj_invalidate(obj);
+}
+
+void lv_dropdown_set_selected_highlight(lv_obj_t * obj, bool en)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ dropdown->selected_highlight = en;
+ if(dropdown->list) lv_obj_invalidate(dropdown->list);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+lv_obj_t * lv_dropdown_get_list(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ return dropdown->list;
+}
+
+const char * lv_dropdown_get_text(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ return dropdown->text;
+}
+
+const char * lv_dropdown_get_options(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ return dropdown->options == NULL ? "" : dropdown->options;
+}
+
+uint16_t lv_dropdown_get_selected(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ return dropdown->sel_opt_id;
+}
+
+uint16_t lv_dropdown_get_option_cnt(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ return dropdown->option_cnt;
+}
+
+void lv_dropdown_get_selected_str(const lv_obj_t * obj, char * buf, uint32_t buf_size)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ uint32_t i;
+ uint32_t line = 0;
+ size_t txt_len;
+
+ if(dropdown->options) {
+ txt_len = strlen(dropdown->options);
+ }
+ else {
+ buf[0] = '\0';
+ return;
+ }
+
+ for(i = 0; i < txt_len && line != dropdown->sel_opt_id_orig; i++) {
+ if(dropdown->options[i] == '\n') line++;
+ }
+
+ uint32_t c;
+ for(c = 0; i < txt_len && dropdown->options[i] != '\n'; c++, i++) {
+ if(buf_size && c >= buf_size - 1) {
+ LV_LOG_WARN("lv_dropdown_get_selected_str: the buffer was too small");
+ break;
+ }
+ buf[c] = dropdown->options[i];
+ }
+
+ buf[c] = '\0';
+}
+
+int32_t lv_dropdown_get_option_index(lv_obj_t * obj, const char * option)
+{
+ const char * opts = lv_dropdown_get_options(obj);
+ uint32_t char_i = 0;
+ uint32_t opt_i = 0;
+ const char * start = opts;
+
+ while(start[char_i] != '\0') {
+ for(char_i = 0; (start[char_i] != '\n') && (start[char_i] != '\0'); char_i++);
+
+ if(memcmp(start, option, LV_MIN(strlen(option), char_i)) == 0) return opt_i;
+ start = &start[char_i];
+ if(start[0] == '\n') start++;
+ opt_i++;
+ }
+
+ return -1;
+}
+
+
+const char * lv_dropdown_get_symbol(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ return dropdown->symbol;
+}
+
+bool lv_dropdown_get_selected_highlight(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ return dropdown->selected_highlight;
+}
+
+lv_dir_t lv_dropdown_get_dir(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ return dropdown->dir;
+}
+
+/*=====================
+ * Other functions
+ *====================*/
+
+void lv_dropdown_open(lv_obj_t * dropdown_obj)
+{
+ LV_ASSERT_OBJ(dropdown_obj, MY_CLASS);
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+
+ lv_obj_add_state(dropdown_obj, LV_STATE_CHECKED);
+ lv_obj_set_parent(dropdown->list, lv_obj_get_screen(dropdown_obj));
+ lv_obj_move_to_index(dropdown->list, -1);
+ lv_obj_clear_flag(dropdown->list, LV_OBJ_FLAG_HIDDEN);
+
+ /*To allow styling the list*/
+ lv_event_send(dropdown_obj, LV_EVENT_READY, NULL);
+
+ lv_obj_t * label = get_label(dropdown_obj);
+ lv_label_set_text_static(label, dropdown->options);
+ lv_obj_set_width(dropdown->list, LV_SIZE_CONTENT);
+
+ lv_obj_update_layout(label);
+ /*Set smaller width to the width of the button*/
+ if(lv_obj_get_width(dropdown->list) <= lv_obj_get_width(dropdown_obj) &&
+ (dropdown->dir == LV_DIR_TOP || dropdown->dir == LV_DIR_BOTTOM)) {
+ lv_obj_set_width(dropdown->list, lv_obj_get_width(dropdown_obj));
+ }
+
+ lv_coord_t label_h = lv_obj_get_height(label);
+ lv_coord_t border_width = lv_obj_get_style_border_width(dropdown->list, LV_PART_MAIN);
+ lv_coord_t top = lv_obj_get_style_pad_top(dropdown->list, LV_PART_MAIN) + border_width;
+ lv_coord_t bottom = lv_obj_get_style_pad_bottom(dropdown->list, LV_PART_MAIN) + border_width;
+
+ lv_coord_t list_fit_h = label_h + top + bottom;
+ lv_coord_t list_h = list_fit_h;
+
+ lv_dir_t dir = dropdown->dir;
+ /*No space on the bottom? See if top is better.*/
+ if(dropdown->dir == LV_DIR_BOTTOM) {
+ if(dropdown_obj->coords.y2 + list_h > LV_VER_RES) {
+ if(dropdown_obj->coords.y1 > LV_VER_RES - dropdown_obj->coords.y2) {
+ /*There is more space on the top, so make it drop up*/
+ dir = LV_DIR_TOP;
+ list_h = dropdown_obj->coords.y1 - 1;
+ }
+ else {
+ list_h = LV_VER_RES - dropdown_obj->coords.y2 - 1 ;
+ }
+ }
+ }
+ /*No space on the top? See if bottom is better.*/
+ else if(dropdown->dir == LV_DIR_TOP) {
+ if(dropdown_obj->coords.y1 - list_h < 0) {
+ if(dropdown_obj->coords.y1 < LV_VER_RES - dropdown_obj->coords.y2) {
+ /*There is more space on the top, so make it drop up*/
+ dir = LV_DIR_BOTTOM;
+ list_h = LV_VER_RES - dropdown_obj->coords.y2;
+ }
+ else {
+ list_h = dropdown_obj->coords.y1;
+ }
+ }
+ }
+
+ if(list_h > list_fit_h) list_h = list_fit_h;
+ lv_obj_set_height(dropdown->list, list_h);
+
+ position_to_selected(dropdown_obj);
+
+ if(dir == LV_DIR_BOTTOM) lv_obj_align_to(dropdown->list, dropdown_obj, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
+ else if(dir == LV_DIR_TOP) lv_obj_align_to(dropdown->list, dropdown_obj, LV_ALIGN_OUT_TOP_LEFT, 0, 0);
+ else if(dir == LV_DIR_LEFT) lv_obj_align_to(dropdown->list, dropdown_obj, LV_ALIGN_OUT_LEFT_TOP, 0, 0);
+ else if(dir == LV_DIR_RIGHT) lv_obj_align_to(dropdown->list, dropdown_obj, LV_ALIGN_OUT_RIGHT_TOP, 0, 0);
+
+ lv_obj_update_layout(dropdown->list);
+
+ if(dropdown->dir == LV_DIR_LEFT || dropdown->dir == LV_DIR_RIGHT) {
+ lv_coord_t y1 = lv_obj_get_y(dropdown->list);
+ lv_coord_t y2 = lv_obj_get_y2(dropdown->list);
+ if(y2 >= LV_VER_RES) {
+ lv_obj_set_y(dropdown->list, y1 - (y2 - LV_VER_RES) - 1);
+ }
+ }
+
+ lv_text_align_t align = lv_obj_calculate_style_text_align(label, LV_PART_MAIN, dropdown->options);
+
+ switch(align) {
+ default:
+ case LV_TEXT_ALIGN_LEFT:
+ lv_obj_align(label, LV_ALIGN_TOP_LEFT, 0, 0);
+ break;
+ case LV_TEXT_ALIGN_RIGHT:
+ lv_obj_align(label, LV_ALIGN_TOP_RIGHT, 0, 0);
+ break;
+ case LV_TEXT_ALIGN_CENTER:
+ lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 0);
+ break;
+
+ }
+}
+
+void lv_dropdown_close(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_obj_clear_state(obj, LV_STATE_CHECKED);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ dropdown->pr_opt_id = LV_DROPDOWN_PR_NONE;
+ lv_obj_add_flag(dropdown->list, LV_OBJ_FLAG_HIDDEN);
+
+ lv_event_send(obj, LV_EVENT_CANCEL, NULL);
+}
+
+bool lv_dropdown_is_open(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ return lv_obj_has_flag(dropdown->list, LV_OBJ_FLAG_HIDDEN) ? false : true;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static lv_obj_t * lv_dropdown_list_create(lv_obj_t * parent)
+{
+ LV_LOG_INFO("begin");
+ lv_obj_t * obj = lv_obj_class_create_obj(&lv_dropdownlist_class, parent);
+ lv_obj_class_init_obj(obj);
+ return obj;
+}
+
+static void lv_dropdown_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ /*Initialize the allocated 'ext'*/
+ dropdown->list = NULL;
+ dropdown->options = NULL;
+ dropdown->symbol = LV_SYMBOL_DOWN;
+ dropdown->text = NULL;
+ dropdown->static_txt = 1;
+ dropdown->selected_highlight = 1;
+ dropdown->sel_opt_id = 0;
+ dropdown->sel_opt_id_orig = 0;
+ dropdown->pr_opt_id = LV_DROPDOWN_PR_NONE;
+ dropdown->option_cnt = 0;
+ dropdown->dir = LV_DIR_BOTTOM;
+
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
+ lv_dropdown_set_options_static(obj, "Option 1\nOption 2\nOption 3");
+
+ dropdown->list = lv_dropdown_list_create(lv_obj_get_screen(obj));
+ lv_dropdown_list_t * list = (lv_dropdown_list_t *)dropdown->list;
+ list->dropdown = obj;
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_dropdown_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ if(dropdown->list) {
+ lv_obj_del(dropdown->list);
+ dropdown->list = NULL;
+ }
+
+ if(!dropdown->static_txt) {
+ lv_mem_free(dropdown->options);
+ dropdown->options = NULL;
+ }
+}
+
+static void lv_dropdownlist_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_IGNORE_LAYOUT);
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);
+
+ lv_label_create(obj);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_dropdownlist_destructor(const lv_obj_class_t * class_p, lv_obj_t * list_obj)
+{
+ LV_UNUSED(class_p);
+ lv_dropdown_list_t * list = (lv_dropdown_list_t *)list_obj;
+ lv_obj_t * dropdown_obj = list->dropdown;
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+ dropdown->list = NULL;
+}
+
+static void lv_dropdown_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*/
+ 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_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+
+ if(code == LV_EVENT_FOCUSED) {
+ lv_group_t * g = lv_obj_get_group(obj);
+ bool editing = lv_group_get_editing(g);
+ lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
+
+ /*Encoders need special handling*/
+ if(indev_type == LV_INDEV_TYPE_ENCODER) {
+ /*Open the list if editing*/
+ if(editing) {
+ lv_dropdown_open(obj);
+ }
+ /*Close the list if navigating*/
+ else {
+ dropdown->sel_opt_id = dropdown->sel_opt_id_orig;
+ lv_dropdown_close(obj);
+ }
+ }
+ }
+ else if(code == LV_EVENT_DEFOCUSED || code == LV_EVENT_LEAVE) {
+ lv_dropdown_close(obj);
+ }
+ else if(code == LV_EVENT_RELEASED) {
+ res = btn_release_handler(obj);
+ if(res != LV_RES_OK) return;
+ }
+ else if(code == LV_EVENT_STYLE_CHANGED) {
+ lv_obj_refresh_self_size(obj);
+ }
+ else if(code == LV_EVENT_SIZE_CHANGED) {
+ lv_obj_refresh_self_size(obj);
+ }
+ else if(code == LV_EVENT_GET_SELF_SIZE) {
+ lv_point_t * p = lv_event_get_param(e);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ p->y = lv_font_get_line_height(font);
+ }
+ else if(code == LV_EVENT_KEY) {
+ char c = *((char *)lv_event_get_param(e));
+ if(c == LV_KEY_RIGHT || c == LV_KEY_DOWN) {
+ if(!lv_dropdown_is_open(obj)) {
+ lv_dropdown_open(obj);
+ }
+ else if(dropdown->sel_opt_id + 1 < dropdown->option_cnt) {
+ dropdown->sel_opt_id++;
+ position_to_selected(obj);
+ }
+ }
+ else if(c == LV_KEY_LEFT || c == LV_KEY_UP) {
+
+ if(!lv_dropdown_is_open(obj)) {
+ lv_dropdown_open(obj);
+ }
+ else if(dropdown->sel_opt_id > 0) {
+ dropdown->sel_opt_id--;
+ position_to_selected(obj);
+ }
+ }
+ else if(c == LV_KEY_ESC) {
+ dropdown->sel_opt_id = dropdown->sel_opt_id_orig;
+ lv_dropdown_close(obj);
+ }
+ else if(c == LV_KEY_ENTER) {
+ /* Handle the ENTER key only if it was send by an other object.
+ * Do no process it if ENTER is sent by the dropdown because it's handled in LV_EVENT_RELEASED */
+ lv_obj_t * indev_obj = lv_indev_get_obj_act();
+ if(indev_obj != obj) {
+ res = btn_release_handler(obj);
+ if(res != LV_RES_OK) return;
+ }
+ }
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ draw_main(e);
+ }
+}
+
+static void lv_dropdown_list_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_POST) {
+ res = lv_obj_event_base(MY_CLASS_LIST, e);
+ if(res != LV_RES_OK) return;
+ }
+ lv_obj_t * list = lv_event_get_target(e);
+ lv_obj_t * dropdown_obj = ((lv_dropdown_list_t *)list)->dropdown;
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+
+ if(code == LV_EVENT_RELEASED) {
+ if(lv_indev_get_scroll_obj(lv_indev_get_act()) == NULL) {
+ list_release_handler(list);
+ }
+ }
+ else if(code == LV_EVENT_PRESSED) {
+ list_press_handler(list);
+ }
+ else if(code == LV_EVENT_SCROLL_BEGIN) {
+ dropdown->pr_opt_id = LV_DROPDOWN_PR_NONE;
+ lv_obj_invalidate(list);
+ }
+ else if(code == LV_EVENT_DRAW_POST) {
+ draw_list(e);
+ res = lv_obj_event_base(MY_CLASS_LIST, e);
+ if(res != LV_RES_OK) return;
+ }
+}
+
+
+static void draw_main(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
+ lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
+ lv_coord_t right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN) + border_width;
+ lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
+
+ lv_draw_label_dsc_t symbol_dsc;
+ lv_draw_label_dsc_init(&symbol_dsc);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_INDICATOR, &symbol_dsc);
+
+ /*If no text specified use the selected option*/
+ const char * opt_txt;
+ if(dropdown->text) opt_txt = dropdown->text;
+ else {
+ char * buf = lv_mem_buf_get(128);
+ lv_dropdown_get_selected_str(obj, buf, 128);
+ opt_txt = buf;
+ }
+
+ bool symbol_to_left = false;
+ if(dropdown->dir == LV_DIR_LEFT) symbol_to_left = true;
+ if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) == LV_BASE_DIR_RTL) symbol_to_left = true;
+
+ if(dropdown->symbol) {
+ lv_img_src_t symbol_type = lv_img_src_get_type(dropdown->symbol);
+ lv_coord_t symbol_w;
+ lv_coord_t symbol_h;
+ if(symbol_type == LV_IMG_SRC_SYMBOL) {
+ lv_point_t size;
+ lv_txt_get_size(&size, dropdown->symbol, symbol_dsc.font, symbol_dsc.letter_space, symbol_dsc.line_space, LV_COORD_MAX,
+ symbol_dsc.flag);
+ symbol_w = size.x;
+ symbol_h = size.y;
+ }
+ else {
+ lv_img_header_t header;
+ lv_res_t res = lv_img_decoder_get_info(dropdown->symbol, &header);
+ if(res == LV_RES_OK) {
+ symbol_w = header.w;
+ symbol_h = header.h;
+ }
+ else {
+ symbol_w = -1;
+ symbol_h = -1;
+ }
+ }
+
+ lv_area_t symbol_area;
+ if(symbol_to_left) {
+ symbol_area.x1 = obj->coords.x1 + left;
+ symbol_area.x2 = symbol_area.x1 + symbol_w - 1;
+ }
+ else {
+ symbol_area.x1 = obj->coords.x2 - right - symbol_w;
+ symbol_area.x2 = symbol_area.x1 + symbol_w - 1;
+ }
+
+ if(symbol_type == LV_IMG_SRC_SYMBOL) {
+ symbol_area.y1 = obj->coords.y1 + top;
+ symbol_area.y2 = symbol_area.y1 + symbol_h - 1;
+ lv_draw_label(draw_ctx, &symbol_dsc, &symbol_area, dropdown->symbol, NULL);
+ }
+ else {
+ symbol_area.y1 = obj->coords.y1 + (lv_obj_get_height(obj) - symbol_h) / 2;
+ symbol_area.y2 = symbol_area.y1 + symbol_h - 1;
+ lv_draw_img_dsc_t img_dsc;
+ lv_draw_img_dsc_init(&img_dsc);
+ lv_obj_init_draw_img_dsc(obj, LV_PART_INDICATOR, &img_dsc);
+ img_dsc.pivot.x = symbol_w / 2;
+ img_dsc.pivot.y = symbol_h / 2;
+ img_dsc.angle = lv_obj_get_style_transform_angle(obj, LV_PART_INDICATOR);
+ lv_draw_img(draw_ctx, &img_dsc, &symbol_area, dropdown->symbol);
+ }
+ }
+
+ lv_draw_label_dsc_t label_dsc;
+ lv_draw_label_dsc_init(&label_dsc);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_MAIN, &label_dsc);
+
+ lv_point_t size;
+ lv_txt_get_size(&size, opt_txt, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX,
+ label_dsc.flag);
+
+ lv_area_t txt_area;
+ txt_area.y1 = obj->coords.y1 + top;
+ txt_area.y2 = txt_area.y1 + size.y;
+ /*Center align the text if no symbol*/
+ if(dropdown->symbol == NULL) {
+ txt_area.x1 = obj->coords.x1 + (lv_obj_get_width(obj) - size.x) / 2;
+ txt_area.x2 = txt_area.x1 + size.x;
+ }
+ else {
+ /*Text to the right*/
+ if(symbol_to_left) {
+ txt_area.x1 = obj->coords.x2 - right - size.x;
+ txt_area.x2 = txt_area.x1 + size.x;
+ }
+ else {
+ txt_area.x1 = obj->coords.x1 + left;
+ txt_area.x2 = txt_area.x1 + size.x;
+ }
+ }
+ lv_draw_label(draw_ctx, &label_dsc, &txt_area, opt_txt, NULL);
+
+ if(dropdown->text == NULL) {
+ lv_mem_buf_release((char *)opt_txt);
+ }
+}
+
+static void draw_list(lv_event_t * e)
+{
+ lv_obj_t * list_obj = lv_event_get_target(e);
+ lv_dropdown_list_t * list = (lv_dropdown_list_t *)list_obj;
+ lv_obj_t * dropdown_obj = list->dropdown;
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ /* Clip area might be too large too to shadow but
+ * the selected option can be drawn on only the background*/
+ lv_area_t clip_area_core;
+ bool has_common;
+ has_common = _lv_area_intersect(&clip_area_core, draw_ctx->clip_area, &dropdown->list->coords);
+ if(has_common) {
+ const lv_area_t * clip_area_ori = draw_ctx->clip_area;
+ draw_ctx->clip_area = &clip_area_core;
+ if(dropdown->selected_highlight) {
+ if(dropdown->pr_opt_id == dropdown->sel_opt_id) {
+ draw_box(dropdown_obj, draw_ctx, dropdown->pr_opt_id, LV_STATE_CHECKED | LV_STATE_PRESSED);
+ draw_box_label(dropdown_obj, draw_ctx, dropdown->pr_opt_id, LV_STATE_CHECKED | LV_STATE_PRESSED);
+ }
+ else {
+ draw_box(dropdown_obj, draw_ctx, dropdown->pr_opt_id, LV_STATE_PRESSED);
+ draw_box_label(dropdown_obj, draw_ctx, dropdown->pr_opt_id, LV_STATE_PRESSED);
+ draw_box(dropdown_obj, draw_ctx, dropdown->sel_opt_id, LV_STATE_CHECKED);
+ draw_box_label(dropdown_obj, draw_ctx, dropdown->sel_opt_id, LV_STATE_CHECKED);
+ }
+ }
+ else {
+ draw_box(dropdown_obj, draw_ctx, dropdown->pr_opt_id, LV_STATE_PRESSED);
+ draw_box_label(dropdown_obj, draw_ctx, dropdown->pr_opt_id, LV_STATE_PRESSED);
+ }
+ draw_ctx->clip_area = clip_area_ori;
+ }
+}
+
+static void draw_box(lv_obj_t * dropdown_obj, lv_draw_ctx_t * draw_ctx, uint16_t id, lv_state_t state)
+{
+ if(id == LV_DROPDOWN_PR_NONE) return;
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+ lv_obj_t * list_obj = dropdown->list;
+ lv_state_t state_ori = list_obj->state;
+
+ if(state != list_obj->state) {
+ list_obj->state = state;
+ list_obj->skip_trans = 1;
+ }
+
+ /*Draw a rectangle under the selected item*/
+ const lv_font_t * font = lv_obj_get_style_text_font(list_obj, LV_PART_SELECTED);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(list_obj, LV_PART_SELECTED);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+
+ /*Draw the selected*/
+ lv_obj_t * label = get_label(dropdown_obj);
+ lv_area_t rect_area;
+ rect_area.y1 = label->coords.y1;
+ rect_area.y1 += id * (font_h + line_space);
+ rect_area.y1 -= line_space / 2;
+
+ rect_area.y2 = rect_area.y1 + font_h + line_space - 1;
+ rect_area.x1 = dropdown->list->coords.x1;
+ rect_area.x2 = dropdown->list->coords.x2;
+
+ lv_draw_rect_dsc_t sel_rect;
+ lv_draw_rect_dsc_init(&sel_rect);
+ lv_obj_init_draw_rect_dsc(list_obj, LV_PART_SELECTED, &sel_rect);
+ lv_draw_rect(draw_ctx, &sel_rect, &rect_area);
+
+ list_obj->state = state_ori;
+ list_obj->skip_trans = 0;
+}
+
+static void draw_box_label(lv_obj_t * dropdown_obj, lv_draw_ctx_t * draw_ctx, uint16_t id, lv_state_t state)
+{
+ if(id == LV_DROPDOWN_PR_NONE) return;
+
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+ lv_obj_t * list_obj = dropdown->list;
+ lv_state_t state_orig = list_obj->state;
+
+ if(state != list_obj->state) {
+ list_obj->state = state;
+ list_obj->skip_trans = 1;
+ }
+
+ lv_draw_label_dsc_t label_dsc;
+ lv_draw_label_dsc_init(&label_dsc);
+ lv_obj_init_draw_label_dsc(list_obj, LV_PART_SELECTED, &label_dsc);
+
+ label_dsc.line_space = lv_obj_get_style_text_line_space(list_obj,
+ LV_PART_SELECTED); /*Line space should come from the list*/
+
+ lv_obj_t * label = get_label(dropdown_obj);
+ if(label == NULL) return;
+
+ lv_coord_t font_h = lv_font_get_line_height(label_dsc.font);
+
+ lv_area_t area_sel;
+ area_sel.y1 = label->coords.y1;
+ area_sel.y1 += id * (font_h + label_dsc.line_space);
+ area_sel.y1 -= label_dsc.line_space / 2;
+
+ area_sel.y2 = area_sel.y1 + font_h + label_dsc.line_space - 1;
+ area_sel.x1 = list_obj->coords.x1;
+ area_sel.x2 = list_obj->coords.x2;
+ lv_area_t mask_sel;
+ bool area_ok;
+ area_ok = _lv_area_intersect(&mask_sel, draw_ctx->clip_area, &area_sel);
+ if(area_ok) {
+ const lv_area_t * clip_area_ori = draw_ctx->clip_area;
+ draw_ctx->clip_area = &mask_sel;
+ lv_draw_label(draw_ctx, &label_dsc, &label->coords, lv_label_get_text(label), NULL);
+ draw_ctx->clip_area = clip_area_ori;
+ }
+ list_obj->state = state_orig;
+ list_obj->skip_trans = 0;
+}
+
+
+static lv_res_t btn_release_handler(lv_obj_t * obj)
+{
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ lv_indev_t * indev = lv_indev_get_act();
+ if(lv_indev_get_scroll_obj(indev) == NULL) {
+ if(lv_dropdown_is_open(obj)) {
+ lv_dropdown_close(obj);
+ if(dropdown->sel_opt_id_orig != dropdown->sel_opt_id) {
+ dropdown->sel_opt_id_orig = dropdown->sel_opt_id;
+ lv_res_t res;
+ uint32_t id = dropdown->sel_opt_id; /*Just to use uint32_t in event data*/
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &id);
+ if(res != LV_RES_OK) return res;
+ lv_obj_invalidate(obj);
+ }
+ lv_indev_type_t indev_type = lv_indev_get_type(indev);
+ if(indev_type == LV_INDEV_TYPE_ENCODER) {
+ lv_group_set_editing(lv_obj_get_group(obj), false);
+ }
+ }
+ else {
+ lv_dropdown_open(obj);
+ }
+ }
+ else {
+ dropdown->sel_opt_id = dropdown->sel_opt_id_orig;
+ lv_obj_invalidate(obj);
+ }
+ return LV_RES_OK;
+}
+
+/**
+ * Called when a drop down list is released to open it or set new option
+ * @param list pointer to the drop down list's list
+ * @return LV_RES_INV if the list is not being deleted in the user callback. Else LV_RES_OK
+ */
+static lv_res_t list_release_handler(lv_obj_t * list_obj)
+{
+ lv_dropdown_list_t * list = (lv_dropdown_list_t *) list_obj;
+ lv_obj_t * dropdown_obj = list->dropdown;
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+
+ lv_indev_t * indev = lv_indev_get_act();
+ /*Leave edit mode once a new item is selected*/
+ if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) {
+ dropdown->sel_opt_id_orig = dropdown->sel_opt_id;
+ lv_group_t * g = lv_obj_get_group(dropdown_obj);
+ if(lv_group_get_editing(g)) {
+ lv_group_set_editing(g, false);
+ }
+ }
+
+ /*Search the clicked option (For KEYPAD and ENCODER the new value should be already set)*/
+ if(lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER || lv_indev_get_type(indev) == LV_INDEV_TYPE_BUTTON) {
+ lv_point_t p;
+ lv_indev_get_point(indev, &p);
+ dropdown->sel_opt_id = get_id_on_point(dropdown_obj, p.y);
+ dropdown->sel_opt_id_orig = dropdown->sel_opt_id;
+ }
+
+ lv_dropdown_close(dropdown_obj);
+
+ /*Invalidate to refresh the text*/
+ if(dropdown->text == NULL) lv_obj_invalidate(dropdown_obj);
+
+ uint32_t id = dropdown->sel_opt_id; /*Just to use uint32_t in event data*/
+ lv_res_t res = lv_event_send(dropdown_obj, LV_EVENT_VALUE_CHANGED, &id);
+ if(res != LV_RES_OK) return res;
+
+ return LV_RES_OK;
+}
+
+static void list_press_handler(lv_obj_t * list_obj)
+{
+ lv_dropdown_list_t * list = (lv_dropdown_list_t *) list_obj;
+ lv_obj_t * dropdown_obj = list->dropdown;
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+
+ lv_indev_t * indev = lv_indev_get_act();
+ if(indev && (lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER || lv_indev_get_type(indev) == LV_INDEV_TYPE_BUTTON)) {
+ lv_point_t p;
+ lv_indev_get_point(indev, &p);
+ dropdown->pr_opt_id = get_id_on_point(dropdown_obj, p.y);
+ lv_obj_invalidate(list_obj);
+ }
+}
+
+static uint16_t get_id_on_point(lv_obj_t * dropdown_obj, lv_coord_t y)
+{
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+ lv_obj_t * label = get_label(dropdown_obj);
+ if(label == NULL) return 0;
+ y -= label->coords.y1;
+
+ const lv_font_t * font = lv_obj_get_style_text_font(label, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(label, LV_PART_MAIN);
+
+ y += line_space / 2;
+ lv_coord_t h = font_h + line_space;
+
+ uint16_t opt = y / h;
+
+ if(opt >= dropdown->option_cnt) opt = dropdown->option_cnt - 1;
+ return opt;
+}
+
+/**
+ * Set the position of list when it is closed to show the selected item
+ * @param ddlist pointer to a drop down list
+ */
+static void position_to_selected(lv_obj_t * dropdown_obj)
+{
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)dropdown_obj;
+
+ lv_obj_t * label = get_label(dropdown_obj);
+ if(label == NULL) return;
+
+ if(lv_obj_get_height(label) <= lv_obj_get_content_height(dropdown_obj)) return;
+
+ const lv_font_t * font = lv_obj_get_style_text_font(label, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(label, LV_PART_MAIN);
+ lv_coord_t unit_h = font_h + line_space;
+ lv_coord_t line_y1 = dropdown->sel_opt_id * unit_h;
+
+ /*Scroll to the selected option*/
+ lv_obj_scroll_to_y(dropdown->list, line_y1, LV_ANIM_OFF);
+ lv_obj_invalidate(dropdown->list);
+}
+
+static lv_obj_t * get_label(const lv_obj_t * obj)
+{
+ lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;
+ if(dropdown->list == NULL) return NULL;
+
+ return lv_obj_get_child(dropdown->list, 0);
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_dropdown.h b/lib/lvgl/src/widgets/lv_dropdown.h
new file mode 100644
index 00000000..0c55e862
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_dropdown.h
@@ -0,0 +1,254 @@
+/**
+ * @file lv_dropdown.h
+ *
+ */
+
+#ifndef LV_DROPDOWN_H
+#define LV_DROPDOWN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_DROPDOWN != 0
+
+/*Testing of dependencies*/
+
+#if LV_USE_LABEL == 0
+#error "lv_dropdown: lv_label is required. Enable it in lv_conf.h (LV_USE_LABEL 1)"
+#endif
+
+#include "../widgets/lv_label.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define LV_DROPDOWN_POS_LAST 0xFFFF
+LV_EXPORT_CONST_INT(LV_DROPDOWN_POS_LAST);
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ lv_obj_t obj;
+ lv_obj_t * list; /**< The dropped down list*/
+ const char * text; /**< Text to display on the dropdown's button*/
+ const void * symbol; /**< Arrow or other icon when the drop-down list is closed*/
+ char * options; /**< Options in a '\n' separated list*/
+ uint16_t option_cnt; /**< Number of options*/
+ uint16_t sel_opt_id; /**< Index of the currently selected option*/
+ uint16_t sel_opt_id_orig; /**< Store the original index on focus*/
+ uint16_t pr_opt_id; /**< Index of the currently pressed option*/
+ lv_dir_t dir : 4; /**< Direction in which the list should open*/
+ uint8_t static_txt : 1; /**< 1: Only a pointer is saved in `options`*/
+ uint8_t selected_highlight: 1; /**< 1: Make the selected option highlighted in the list*/
+} lv_dropdown_t;
+
+typedef struct {
+ lv_obj_t obj;
+ lv_obj_t * dropdown;
+} lv_dropdown_list_t;
+
+extern const lv_obj_class_t lv_dropdown_class;
+extern const lv_obj_class_t lv_dropdownlist_class;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a drop-down list object
+ * @param parent pointer to an object, it will be the parent of the new drop-down list
+ * @return pointer to the created drop-down list
+ */
+lv_obj_t * lv_dropdown_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set text of the drop-down list's button.
+ * If set to `NULL` the selected option's text will be displayed on the button.
+ * If set to a specific text then that text will be shown regardless of the selected option.
+ * @param obj pointer to a drop-down list object
+ * @param txt the text as a string (Only its pointer is saved)
+ */
+void lv_dropdown_set_text(lv_obj_t * obj, const char * txt);
+
+/**
+ * Set the options in a drop-down list from a string.
+ * The options will be copied and saved in the object so the `options` can be destroyed after calling this function
+ * @param obj pointer to drop-down list object
+ * @param options a string with '\n' separated options. E.g. "One\nTwo\nThree"
+ */
+void lv_dropdown_set_options(lv_obj_t * obj, const char * options);
+
+/**
+ * Set the options in a drop-down list from a static string (global, static or dynamically allocated).
+ * Only the pointer of the option string will be saved.
+ * @param obj pointer to drop-down list object
+ * @param options a static string with '\n' separated options. E.g. "One\nTwo\nThree"
+ */
+void lv_dropdown_set_options_static(lv_obj_t * obj, const char * options);
+
+/**
+ * Add an options to a drop-down list from a string. Only works for non-static options.
+ * @param obj pointer to drop-down list object
+ * @param option a string without '\n'. E.g. "Four"
+ * @param pos the insert position, indexed from 0, LV_DROPDOWN_POS_LAST = end of string
+ */
+void lv_dropdown_add_option(lv_obj_t * obj, const char * option, uint32_t pos);
+
+/**
+ * Clear all options in a drop-down list. Works with both static and dynamic options.
+ * @param obj pointer to drop-down list object
+ */
+void lv_dropdown_clear_options(lv_obj_t * obj);
+
+/**
+ * Set the selected option
+ * @param obj pointer to drop-down list object
+ * @param sel_opt id of the selected option (0 ... number of option - 1);
+ */
+void lv_dropdown_set_selected(lv_obj_t * obj, uint16_t sel_opt);
+
+/**
+ * Set the direction of the a drop-down list
+ * @param obj pointer to a drop-down list object
+ * @param dir LV_DIR_LEFT/RIGHT/TOP/BOTTOM
+ */
+void lv_dropdown_set_dir(lv_obj_t * obj, lv_dir_t dir);
+
+/**
+ * Set an arrow or other symbol to display when on drop-down list's button. Typically a down caret or arrow.
+ * @param obj pointer to drop-down list object
+ * @param symbol a text like `LV_SYMBOL_DOWN`, an image (pointer or path) or NULL to not draw symbol icon
+ * @note angle and zoom transformation can be applied if the symbol is an image.
+ * E.g. when drop down is checked (opened) rotate the symbol by 180 degree
+ */
+void lv_dropdown_set_symbol(lv_obj_t * obj, const void * symbol);
+
+/**
+ * Set whether the selected option in the list should be highlighted or not
+ * @param obj pointer to drop-down list object
+ * @param en true: highlight enabled; false: disabled
+ */
+void lv_dropdown_set_selected_highlight(lv_obj_t * obj, bool en);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the list of a drop-down to allow styling or other modifications
+ * @param obj pointer to a drop-down list object
+ * @return pointer to the list of the drop-down
+ */
+lv_obj_t * lv_dropdown_get_list(lv_obj_t * obj);
+
+/**
+ * Get text of the drop-down list's button.
+ * @param obj pointer to a drop-down list object
+ * @return the text as string, `NULL` if no text
+ */
+const char * lv_dropdown_get_text(lv_obj_t * obj);
+
+/**
+ * Get the options of a drop-down list
+ * @param obj pointer to drop-down list object
+ * @return the options separated by '\n'-s (E.g. "Option1\nOption2\nOption3")
+ */
+const char * lv_dropdown_get_options(const lv_obj_t * obj);
+
+/**
+ * Get the index of the selected option
+ * @param obj pointer to drop-down list object
+ * @return index of the selected option (0 ... number of option - 1);
+ */
+uint16_t lv_dropdown_get_selected(const lv_obj_t * obj);
+
+/**
+ * Get the total number of options
+ * @param obj pointer to drop-down list object
+ * @return the total number of options in the list
+ */
+uint16_t lv_dropdown_get_option_cnt(const lv_obj_t * obj);
+
+/**
+ * Get the current selected option as a string
+ * @param obj pointer to drop-down object
+ * @param buf pointer to an array to store the string
+ * @param buf_size size of `buf` in bytes. 0: to ignore it.
+ */
+void lv_dropdown_get_selected_str(const lv_obj_t * obj, char * buf, uint32_t buf_size);
+
+/**
+ * Get the index of an option.
+ * @param obj pointer to drop-down object
+ * @param option an option as string
+ * @return index of `option` in the list of all options. -1 if not found.
+ */
+int32_t lv_dropdown_get_option_index(lv_obj_t * obj, const char * option);
+
+/**
+ * Get the symbol on the drop-down list. Typically a down caret or arrow.
+ * @param obj pointer to drop-down list object
+ * @return the symbol or NULL if not enabled
+ */
+const char * lv_dropdown_get_symbol(lv_obj_t * obj);
+
+/**
+ * Get whether the selected option in the list should be highlighted or not
+ * @param obj pointer to drop-down list object
+ * @return true: highlight enabled; false: disabled
+ */
+bool lv_dropdown_get_selected_highlight(lv_obj_t * obj);
+
+/**
+ * Get the direction of the drop-down list
+ * @param obj pointer to a drop-down list object
+ * @return LV_DIR_LEF/RIGHT/TOP/BOTTOM
+ */
+lv_dir_t lv_dropdown_get_dir(const lv_obj_t * obj);
+
+/*=====================
+ * Other functions
+ *====================*/
+
+/**
+ * Open the drop.down list
+ * @param obj pointer to drop-down list object
+ */
+void lv_dropdown_open(lv_obj_t * dropdown_obj);
+
+/**
+ * Close (Collapse) the drop-down list
+ * @param obj pointer to drop-down list object
+ */
+void lv_dropdown_close(lv_obj_t * obj);
+
+/**
+ * Tells whether the list is opened or not
+ * @param obj pointer to a drop-down list object
+ * @return true if the list os opened
+ */
+bool lv_dropdown_is_open(lv_obj_t * obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_DROPDOWN*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_DROPDOWN_H*/
diff --git a/lib/lvgl/src/widgets/lv_img.c b/lib/lvgl/src/widgets/lv_img.c
new file mode 100644
index 00000000..f47a789e
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_img.c
@@ -0,0 +1,693 @@
+/**
+ * @file lv_img.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_img.h"
+#if LV_USE_IMG != 0
+
+#include "../core/lv_disp.h"
+#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"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_img_class
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_img_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_img_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_img_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void draw_img(lv_event_t * e);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_img_class = {
+ .constructor_cb = lv_img_constructor,
+ .destructor_cb = lv_img_destructor,
+ .event_cb = lv_img_event,
+ .width_def = LV_SIZE_CONTENT,
+ .height_def = LV_SIZE_CONTENT,
+ .instance_size = sizeof(lv_img_t),
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_img_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
+ *====================*/
+
+void lv_img_set_src(lv_obj_t * obj, const void * src)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_obj_invalidate(obj);
+
+ lv_img_src_t src_type = lv_img_src_get_type(src);
+ lv_img_t * img = (lv_img_t *)obj;
+
+#if LV_USE_LOG && LV_LOG_LEVEL >= LV_LOG_LEVEL_INFO
+ switch(src_type) {
+ case LV_IMG_SRC_FILE:
+ LV_LOG_TRACE("lv_img_set_src: `LV_IMG_SRC_FILE` type found");
+ break;
+ case LV_IMG_SRC_VARIABLE:
+ LV_LOG_TRACE("lv_img_set_src: `LV_IMG_SRC_VARIABLE` type found");
+ break;
+ case LV_IMG_SRC_SYMBOL:
+ LV_LOG_TRACE("lv_img_set_src: `LV_IMG_SRC_SYMBOL` type found");
+ break;
+ default:
+ LV_LOG_WARN("lv_img_set_src: unknown type");
+ }
+#endif
+
+ /*If the new source type is unknown free the memories of the old source*/
+ if(src_type == LV_IMG_SRC_UNKNOWN) {
+ LV_LOG_WARN("lv_img_set_src: unknown image type");
+ if(img->src_type == LV_IMG_SRC_SYMBOL || img->src_type == LV_IMG_SRC_FILE) {
+ lv_mem_free((void *)img->src);
+ }
+ img->src = NULL;
+ img->src_type = LV_IMG_SRC_UNKNOWN;
+ return;
+ }
+
+ lv_img_header_t header;
+ lv_img_decoder_get_info(src, &header);
+
+ /*Save the source*/
+ if(src_type == LV_IMG_SRC_VARIABLE) {
+ /*If memory was allocated because of the previous `src_type` then free it*/
+ if(img->src_type == LV_IMG_SRC_FILE || img->src_type == LV_IMG_SRC_SYMBOL) {
+ lv_mem_free((void *)img->src);
+ }
+ img->src = src;
+ }
+ else if(src_type == LV_IMG_SRC_FILE || src_type == LV_IMG_SRC_SYMBOL) {
+ /*If the new and the old src are the same then it was only a refresh.*/
+ if(img->src != src) {
+ const void * old_src = NULL;
+ /*If memory was allocated because of the previous `src_type` then save its pointer and free after allocation.
+ *It's important to allocate first to be sure the new data will be on a new address.
+ *Else `img_cache` wouldn't see the change in source.*/
+ if(img->src_type == LV_IMG_SRC_FILE || img->src_type == LV_IMG_SRC_SYMBOL) {
+ old_src = img->src;
+ }
+ char * new_str = lv_mem_alloc(strlen(src) + 1);
+ LV_ASSERT_MALLOC(new_str);
+ if(new_str == NULL) return;
+ strcpy(new_str, src);
+ img->src = new_str;
+
+ if(old_src) lv_mem_free((void *)old_src);
+ }
+ }
+
+ if(src_type == LV_IMG_SRC_SYMBOL) {
+ /*`lv_img_dsc_get_info` couldn't set the width and height of a font so set it here*/
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_point_t size;
+ lv_txt_get_size(&size, src, font, letter_space, line_space, LV_COORD_MAX, LV_TEXT_FLAG_NONE);
+ header.w = size.x;
+ header.h = size.y;
+ }
+
+ img->src_type = src_type;
+ img->w = header.w;
+ img->h = header.h;
+ img->cf = header.cf;
+ img->pivot.x = header.w / 2;
+ img->pivot.y = header.h / 2;
+
+ lv_obj_refresh_self_size(obj);
+
+ /*Provide enough room for the rotated corners*/
+ if(img->angle || img->zoom != LV_IMG_ZOOM_NONE) lv_obj_refresh_ext_draw_size(obj);
+
+ lv_obj_invalidate(obj);
+}
+
+void lv_img_set_offset_x(lv_obj_t * obj, lv_coord_t x)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ x = x % img->w;
+
+ img->offset.x = x;
+ lv_obj_invalidate(obj);
+}
+
+void lv_img_set_offset_y(lv_obj_t * obj, lv_coord_t y)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ y = y % img->h;
+
+ img->offset.y = y;
+ lv_obj_invalidate(obj);
+}
+
+void lv_img_set_angle(lv_obj_t * obj, int16_t angle)
+{
+ if(angle < 0 || angle >= 3600) angle = angle % 3600;
+
+ lv_img_t * img = (lv_img_t *)obj;
+ if(angle == img->angle) return;
+
+ lv_obj_update_layout(obj); /*Be sure the object's size is calculated*/
+ lv_coord_t w = lv_obj_get_width(obj);
+ lv_coord_t h = lv_obj_get_height(obj);
+ lv_area_t a;
+ _lv_img_buf_get_transformed_area(&a, w, h, img->angle, img->zoom, &img->pivot);
+ a.x1 += obj->coords.x1;
+ a.y1 += obj->coords.y1;
+ a.x2 += obj->coords.x1;
+ a.y2 += obj->coords.y1;
+ lv_obj_invalidate_area(obj, &a);
+
+ img->angle = angle;
+
+ /* Disable invalidations because lv_obj_refresh_ext_draw_size would invalidate
+ * the whole ext draw area */
+ lv_disp_t * disp = lv_obj_get_disp(obj);
+ lv_disp_enable_invalidation(disp, false);
+ lv_obj_refresh_ext_draw_size(obj);
+ lv_disp_enable_invalidation(disp, true);
+
+ _lv_img_buf_get_transformed_area(&a, w, h, img->angle, img->zoom, &img->pivot);
+ a.x1 += obj->coords.x1;
+ a.y1 += obj->coords.y1;
+ a.x2 += obj->coords.x1;
+ a.y2 += obj->coords.y1;
+ lv_obj_invalidate_area(obj, &a);
+}
+
+void lv_img_set_pivot(lv_obj_t * obj, lv_coord_t x, lv_coord_t y)
+{
+ lv_img_t * img = (lv_img_t *)obj;
+ if(img->pivot.x == x && img->pivot.y == y) return;
+
+ lv_obj_update_layout(obj); /*Be sure the object's size is calculated*/
+ lv_coord_t w = lv_obj_get_width(obj);
+ lv_coord_t h = lv_obj_get_height(obj);
+ lv_area_t a;
+ _lv_img_buf_get_transformed_area(&a, w, h, img->angle, img->zoom, &img->pivot);
+ a.x1 += obj->coords.x1;
+ a.y1 += obj->coords.y1;
+ a.x2 += obj->coords.x1;
+ a.y2 += obj->coords.y1;
+ lv_obj_invalidate_area(obj, &a);
+
+ img->pivot.x = x;
+ img->pivot.y = y;
+
+ /* Disable invalidations because lv_obj_refresh_ext_draw_size would invalidate
+ * the whole ext draw area */
+ lv_disp_t * disp = lv_obj_get_disp(obj);
+ lv_disp_enable_invalidation(disp, false);
+ lv_obj_refresh_ext_draw_size(obj);
+ lv_disp_enable_invalidation(disp, true);
+
+ _lv_img_buf_get_transformed_area(&a, w, h, img->angle, img->zoom, &img->pivot);
+ a.x1 += obj->coords.x1;
+ a.y1 += obj->coords.y1;
+ a.x2 += obj->coords.x1;
+ a.y2 += obj->coords.y1;
+ lv_obj_invalidate_area(obj, &a);
+}
+
+void lv_img_set_zoom(lv_obj_t * obj, uint16_t zoom)
+{
+ lv_img_t * img = (lv_img_t *)obj;
+ if(zoom == img->zoom) return;
+
+ if(zoom == 0) zoom = 1;
+
+ lv_obj_update_layout(obj); /*Be sure the object's size is calculated*/
+ lv_coord_t w = lv_obj_get_width(obj);
+ lv_coord_t h = lv_obj_get_height(obj);
+ lv_area_t a;
+ _lv_img_buf_get_transformed_area(&a, w, h, img->angle, img->zoom >> 8, &img->pivot);
+ a.x1 += obj->coords.x1 - 1;
+ a.y1 += obj->coords.y1 - 1;
+ a.x2 += obj->coords.x1 + 1;
+ a.y2 += obj->coords.y1 + 1;
+ lv_obj_invalidate_area(obj, &a);
+
+ img->zoom = zoom;
+
+ /* Disable invalidations because lv_obj_refresh_ext_draw_size would invalidate
+ * the whole ext draw area */
+ lv_disp_t * disp = lv_obj_get_disp(obj);
+ lv_disp_enable_invalidation(disp, false);
+ lv_obj_refresh_ext_draw_size(obj);
+ lv_disp_enable_invalidation(disp, true);
+
+ _lv_img_buf_get_transformed_area(&a, w, h, img->angle, img->zoom, &img->pivot);
+ a.x1 += obj->coords.x1 - 1;
+ a.y1 += obj->coords.y1 - 1;
+ a.x2 += obj->coords.x1 + 1;
+ a.y2 += obj->coords.y1 + 1;
+ lv_obj_invalidate_area(obj, &a);
+}
+
+void lv_img_set_antialias(lv_obj_t * obj, bool antialias)
+{
+ lv_img_t * img = (lv_img_t *)obj;
+ if(antialias == img->antialias) return;
+
+ img->antialias = antialias;
+ lv_obj_invalidate(obj);
+}
+
+void lv_img_set_size_mode(lv_obj_t * obj, lv_img_size_mode_t mode)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_img_t * img = (lv_img_t *)obj;
+ if(mode == img->obj_size_mode) return;
+
+ img->obj_size_mode = mode;
+ lv_obj_invalidate(obj);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+const void * lv_img_get_src(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ return img->src;
+}
+
+lv_coord_t lv_img_get_offset_x(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ return img->offset.x;
+}
+
+lv_coord_t lv_img_get_offset_y(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ return img->offset.y;
+}
+
+uint16_t lv_img_get_angle(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ return img->angle;
+}
+
+void lv_img_get_pivot(lv_obj_t * obj, lv_point_t * pivot)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ *pivot = img->pivot;
+}
+
+uint16_t lv_img_get_zoom(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ return img->zoom;
+}
+
+bool lv_img_get_antialias(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ return img->antialias ? true : false;
+}
+
+lv_img_size_mode_t lv_img_get_size_mode(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_img_t * img = (lv_img_t *)obj;
+ return img->obj_size_mode;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_img_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_img_t * img = (lv_img_t *)obj;
+
+ img->src = NULL;
+ img->src_type = LV_IMG_SRC_UNKNOWN;
+ img->cf = LV_IMG_CF_UNKNOWN;
+ img->w = lv_obj_get_width(obj);
+ img->h = lv_obj_get_height(obj);
+ img->angle = 0;
+ img->zoom = LV_IMG_ZOOM_NONE;
+ img->antialias = LV_COLOR_DEPTH > 8 ? 1 : 0;
+ img->offset.x = 0;
+ img->offset.y = 0;
+ img->pivot.x = 0;
+ img->pivot.y = 0;
+ img->obj_size_mode = LV_IMG_SIZE_MODE_VIRTUAL;
+
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE);
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_ADV_HITTEST);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_img_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ lv_img_t * img = (lv_img_t *)obj;
+ if(img->src_type == LV_IMG_SRC_FILE || img->src_type == LV_IMG_SRC_SYMBOL) {
+ lv_mem_free((void *)img->src);
+ img->src = NULL;
+ img->src_type = LV_IMG_SRC_UNKNOWN;
+ }
+}
+
+static lv_point_t lv_img_get_transformed_size(lv_obj_t * obj)
+{
+ lv_img_t * img = (lv_img_t *)obj;
+
+
+ lv_area_t area_transform;
+ _lv_img_buf_get_transformed_area(&area_transform, img->w, img->h,
+ img->angle, img->zoom, &img->pivot);
+
+ return (lv_point_t) {
+ lv_area_get_width(&area_transform), lv_area_get_height(&area_transform)
+ };
+}
+
+static void lv_img_event(const lv_obj_class_t * class_p, lv_event_t * e)
+{
+ LV_UNUSED(class_p);
+
+ lv_event_code_t code = lv_event_get_code(e);
+
+ /*Ancestor events will be called during drawing*/
+ if(code != LV_EVENT_DRAW_MAIN && code != LV_EVENT_DRAW_POST) {
+ /*Call the ancestor's event handler*/
+ lv_res_t res = lv_obj_event_base(MY_CLASS, e);
+ if(res != LV_RES_OK) return;
+ }
+
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_img_t * img = (lv_img_t *)obj;
+
+ if(code == LV_EVENT_STYLE_CHANGED) {
+ /*Refresh the file name to refresh the symbol text size*/
+ if(img->src_type == LV_IMG_SRC_SYMBOL) {
+ lv_img_set_src(obj, img->src);
+ }
+ else {
+ /*With transformation it might change*/
+ lv_obj_refresh_ext_draw_size(obj);
+ }
+ }
+ else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
+
+ lv_coord_t * s = lv_event_get_param(e);
+
+ /*If the image has angle provide enough room for the rotated corners*/
+ if(img->angle || img->zoom != LV_IMG_ZOOM_NONE) {
+ lv_area_t a;
+ lv_coord_t w = lv_obj_get_width(obj);
+ lv_coord_t h = lv_obj_get_height(obj);
+ _lv_img_buf_get_transformed_area(&a, w, h, img->angle, img->zoom, &img->pivot);
+ *s = LV_MAX(*s, -a.x1);
+ *s = LV_MAX(*s, -a.y1);
+ *s = LV_MAX(*s, a.x2 - w);
+ *s = LV_MAX(*s, a.y2 - h);
+ }
+ }
+ else if(code == LV_EVENT_HIT_TEST) {
+ lv_hit_test_info_t * info = lv_event_get_param(e);
+
+ /*If the object is exactly image sized (not cropped, not mosaic) and transformed
+ *perform hit test on its transformed area*/
+ if(img->w == lv_obj_get_width(obj) && img->h == lv_obj_get_height(obj) &&
+ (img->zoom != LV_IMG_ZOOM_NONE || img->angle != 0 || img->pivot.x != img->w / 2 || img->pivot.y != img->h / 2)) {
+
+ lv_coord_t w = lv_obj_get_width(obj);
+ lv_coord_t h = lv_obj_get_height(obj);
+ lv_area_t coords;
+ _lv_img_buf_get_transformed_area(&coords, w, h, img->angle, img->zoom, &img->pivot);
+ coords.x1 += obj->coords.x1;
+ coords.y1 += obj->coords.y1;
+ coords.x2 += obj->coords.x1;
+ coords.y2 += obj->coords.y1;
+
+ info->res = _lv_area_is_point_on(&coords, info->point, 0);
+ }
+ else {
+ lv_area_t a;
+ lv_obj_get_click_area(obj, &a);
+ info->res = _lv_area_is_point_on(&a, info->point, 0);
+ }
+ }
+ else if(code == LV_EVENT_GET_SELF_SIZE) {
+ lv_point_t * p = lv_event_get_param(e);
+ if(img->obj_size_mode == LV_IMG_SIZE_MODE_REAL) {
+ *p = lv_img_get_transformed_size(obj);
+ }
+ else {
+ p->x = img->w;
+ p->y = img->h;
+ }
+ }
+ else if(code == LV_EVENT_DRAW_MAIN || code == LV_EVENT_DRAW_POST || code == LV_EVENT_COVER_CHECK) {
+ draw_img(e);
+ }
+}
+
+static void draw_img(lv_event_t * e)
+{
+ lv_event_code_t code = lv_event_get_code(e);
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_img_t * img = (lv_img_t *)obj;
+ if(code == LV_EVENT_COVER_CHECK) {
+ lv_cover_check_info_t * info = lv_event_get_param(e);
+ if(info->res == LV_COVER_RES_MASKED) return;
+ if(img->src_type == LV_IMG_SRC_UNKNOWN || img->src_type == LV_IMG_SRC_SYMBOL) {
+ info->res = LV_COVER_RES_NOT_COVER;
+ return;
+ }
+
+ /*Non true color format might have "holes"*/
+ if(img->cf != LV_IMG_CF_TRUE_COLOR && img->cf != LV_IMG_CF_RAW) {
+ info->res = LV_COVER_RES_NOT_COVER;
+ return;
+ }
+
+ /*With not LV_OPA_COVER images can't cover an area */
+ if(lv_obj_get_style_img_opa(obj, LV_PART_MAIN) != LV_OPA_COVER) {
+ info->res = LV_COVER_RES_NOT_COVER;
+ return;
+ }
+
+ if(img->angle != 0) {
+ info->res = LV_COVER_RES_NOT_COVER;
+ return;
+ }
+
+ const lv_area_t * clip_area = lv_event_get_param(e);
+ if(img->zoom == LV_IMG_ZOOM_NONE) {
+ if(_lv_area_is_in(clip_area, &obj->coords, 0) == false) {
+ info->res = LV_COVER_RES_NOT_COVER;
+ return;
+ }
+ }
+ else {
+ lv_area_t a;
+ _lv_img_buf_get_transformed_area(&a, lv_obj_get_width(obj), lv_obj_get_height(obj), 0, img->zoom, &img->pivot);
+ a.x1 += obj->coords.x1;
+ a.y1 += obj->coords.y1;
+ a.x2 += obj->coords.x1;
+ a.y2 += obj->coords.y1;
+
+ if(_lv_area_is_in(clip_area, &a, 0) == false) {
+ info->res = LV_COVER_RES_NOT_COVER;
+ return;
+ }
+ }
+ }
+ else if(code == LV_EVENT_DRAW_MAIN || code == LV_EVENT_DRAW_POST) {
+
+ lv_coord_t obj_w = lv_obj_get_width(obj);
+ lv_coord_t obj_h = lv_obj_get_height(obj);
+
+ lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
+ lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
+ lv_coord_t pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN) + border_width;
+ lv_coord_t ptop = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
+ lv_coord_t pbottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) + border_width;
+
+ lv_point_t bg_pivot;
+ bg_pivot.x = img->pivot.x + pleft;
+ bg_pivot.y = img->pivot.y + ptop;
+ lv_area_t bg_coords;
+
+ if(img->obj_size_mode == LV_IMG_SIZE_MODE_REAL) {
+ /*Object size equals to transformed image size*/
+ lv_obj_get_coords(obj, &bg_coords);
+ }
+ else {
+ _lv_img_buf_get_transformed_area(&bg_coords, obj_w, obj_h,
+ img->angle, img->zoom, &bg_pivot);
+
+ /*Modify the coordinates to draw the background for the rotated and scaled coordinates*/
+ bg_coords.x1 += obj->coords.x1;
+ bg_coords.y1 += obj->coords.y1;
+ bg_coords.x2 += obj->coords.x1;
+ bg_coords.y2 += obj->coords.y1;
+ }
+
+ lv_area_t ori_coords;
+ lv_area_copy(&ori_coords, &obj->coords);
+ lv_area_copy(&obj->coords, &bg_coords);
+
+ lv_res_t res = lv_obj_event_base(MY_CLASS, e);
+ if(res != LV_RES_OK) return;
+
+ lv_area_copy(&obj->coords, &ori_coords);
+
+ if(code == LV_EVENT_DRAW_MAIN) {
+ if(img->h == 0 || img->w == 0) return;
+ if(img->zoom == 0) return;
+
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ lv_area_t img_max_area;
+ lv_area_copy(&img_max_area, &obj->coords);
+
+ lv_point_t img_size_final = lv_img_get_transformed_size(obj);
+
+ if(img->obj_size_mode == LV_IMG_SIZE_MODE_REAL) {
+ img_max_area.x1 -= ((img->w - img_size_final.x) + 1) / 2;
+ img_max_area.x2 -= ((img->w - img_size_final.x) + 1) / 2;
+ img_max_area.y1 -= ((img->h - img_size_final.y) + 1) / 2;
+ img_max_area.y2 -= ((img->h - img_size_final.y) + 1) / 2;
+ }
+ else {
+ img_max_area.x2 = img_max_area.x1 + lv_area_get_width(&bg_coords) - 1;
+ img_max_area.y2 = img_max_area.y1 + lv_area_get_height(&bg_coords) - 1;
+ }
+
+ img_max_area.x1 += pleft;
+ img_max_area.y1 += ptop;
+ img_max_area.x2 -= pright;
+ img_max_area.y2 -= pbottom;
+
+ if(img->src_type == LV_IMG_SRC_FILE || img->src_type == LV_IMG_SRC_VARIABLE) {
+ 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);
+
+ img_dsc.zoom = img->zoom;
+ img_dsc.angle = img->angle;
+ img_dsc.pivot.x = img->pivot.x;
+ img_dsc.pivot.y = img->pivot.y;
+ img_dsc.antialias = img->antialias;
+
+ lv_area_t img_clip_area;
+ img_clip_area.x1 = bg_coords.x1 + pleft;
+ img_clip_area.y1 = bg_coords.y1 + ptop;
+ img_clip_area.x2 = bg_coords.x2 - pright;
+ img_clip_area.y2 = bg_coords.y2 - pbottom;
+ const lv_area_t * clip_area_ori = draw_ctx->clip_area;
+
+ if(!_lv_area_intersect(&img_clip_area, draw_ctx->clip_area, &img_clip_area)) return;
+ draw_ctx->clip_area = &img_clip_area;
+
+ lv_area_t coords_tmp;
+ coords_tmp.y1 = img_max_area.y1 + img->offset.y;
+ if(coords_tmp.y1 > img_max_area.y1) coords_tmp.y1 -= img->h;
+ coords_tmp.y2 = coords_tmp.y1 + img->h - 1;
+
+ for(; coords_tmp.y1 < img_max_area.y2; coords_tmp.y1 += img_size_final.y, coords_tmp.y2 += img_size_final.y) {
+ coords_tmp.x1 = img_max_area.x1 + img->offset.x;
+ if(coords_tmp.x1 > img_max_area.x1) coords_tmp.x1 -= img->w;
+ coords_tmp.x2 = coords_tmp.x1 + img->w - 1;
+
+ for(; coords_tmp.x1 < img_max_area.x2; coords_tmp.x1 += img_size_final.x, coords_tmp.x2 += img_size_final.x) {
+ lv_draw_img(draw_ctx, &img_dsc, &coords_tmp, img->src);
+ }
+ }
+ draw_ctx->clip_area = clip_area_ori;
+ }
+ else if(img->src_type == LV_IMG_SRC_SYMBOL) {
+ lv_draw_label_dsc_t label_dsc;
+ lv_draw_label_dsc_init(&label_dsc);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_MAIN, &label_dsc);
+
+ lv_draw_label(draw_ctx, &label_dsc, &obj->coords, img->src, NULL);
+ }
+ else {
+ /*Trigger the error handler of image draw*/
+ LV_LOG_WARN("draw_img: image source type is unknown");
+ lv_draw_img(draw_ctx, NULL, &obj->coords, NULL);
+ }
+ }
+ }
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_img.h b/lib/lvgl/src/widgets/lv_img.h
new file mode 100644
index 00000000..eb76c8d9
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_img.h
@@ -0,0 +1,234 @@
+/**
+ * @file lv_img.h
+ *
+ */
+
+#ifndef LV_IMG_H
+#define LV_IMG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_IMG != 0
+
+/*Testing of dependencies*/
+#if LV_USE_LABEL == 0
+#error "lv_img: lv_label is required. Enable it in lv_conf.h (LV_USE_LABEL 1)"
+#endif
+
+#include "../core/lv_obj.h"
+#include "../misc/lv_fs.h"
+#include "../draw/lv_draw.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**
+ * Data of image
+ */
+typedef struct {
+ lv_obj_t obj;
+ const void * src; /*Image source: Pointer to an array or a file or a symbol*/
+ lv_point_t offset;
+ lv_coord_t w; /*Width of the image (Handled by the library)*/
+ lv_coord_t h; /*Height of the image (Handled by the library)*/
+ uint16_t angle; /*rotation angle of the image*/
+ lv_point_t pivot; /*rotation center of the image*/
+ uint16_t zoom; /*256 means no zoom, 512 double size, 128 half size*/
+ uint8_t src_type : 2; /*See: lv_img_src_t*/
+ uint8_t cf : 5; /*Color format from `lv_img_color_format_t`*/
+ uint8_t antialias : 1; /*Apply anti-aliasing in transformations (rotate, zoom)*/
+ uint8_t obj_size_mode: 2; /*Image size mode when image size and object size is different.*/
+} lv_img_t;
+
+extern const lv_obj_class_t lv_img_class;
+
+/**
+ * Image size mode, when image size and object size is different
+ */
+enum {
+ /** Zoom doesn't affect the coordinates of the object,
+ * however if zoomed in the image is drawn out of the its coordinates.
+ * The layout's won't change on zoom */
+ LV_IMG_SIZE_MODE_VIRTUAL = 0,
+
+ /** If the object size is set to SIZE_CONTENT, then object size equals zoomed image size.
+ * It causes layout recalculation.
+ * If the object size is set explicitly, the image will be cropped when zoomed in.*/
+ LV_IMG_SIZE_MODE_REAL,
+};
+
+typedef uint8_t lv_img_size_mode_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create an image object
+ * @param parent pointer to an object, it will be the parent of the new image
+ * @return pointer to the created image
+ */
+lv_obj_t * lv_img_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set the image data to display on the object
+ * @param obj pointer to an image object
+ * @param src_img 1) pointer to an ::lv_img_dsc_t descriptor (converted by LVGL's image converter) (e.g. &my_img) or
+ * 2) path to an image file (e.g. "S:/dir/img.bin")or
+ * 3) a SYMBOL (e.g. LV_SYMBOL_OK)
+ */
+void lv_img_set_src(lv_obj_t * obj, const void * src);
+
+/**
+ * Set an offset for the source of an image so the image will be displayed from the new origin.
+ * @param obj pointer to an image
+ * @param x the new offset along x axis.
+ */
+void lv_img_set_offset_x(lv_obj_t * obj, lv_coord_t x);
+
+/**
+ * Set an offset for the source of an image.
+ * so the image will be displayed from the new origin.
+ * @param obj pointer to an image
+ * @param y the new offset along y axis.
+ */
+void lv_img_set_offset_y(lv_obj_t * obj, lv_coord_t y);
+
+
+/**
+ * Set the rotation angle of the image.
+ * The image will be rotated around the set pivot set by `lv_img_set_pivot()`
+ * Note that indexed and alpha only images can't be transformed.
+ * @param obj pointer to an image object
+ * @param angle rotation angle in degree with 0.1 degree resolution (0..3600: clock wise)
+ */
+void lv_img_set_angle(lv_obj_t * obj, int16_t angle);
+
+/**
+ * Set the rotation center of the image.
+ * The image will be rotated around this point.
+ * @param obj pointer to an image object
+ * @param x rotation center x of the image
+ * @param y rotation center y of the image
+ */
+void lv_img_set_pivot(lv_obj_t * obj, lv_coord_t x, lv_coord_t y);
+
+
+/**
+ * Set the zoom factor of the image.
+ * Note that indexed and alpha only images can't be transformed.
+ * @param img pointer to an image object
+ * @param zoom the zoom factor.
+ * @example 256 or LV_ZOOM_IMG_NONE for no zoom
+ * @example <256: scale down
+ * @example >256 scale up
+ * @example 128 half size
+ * @example 512 double size
+ */
+void lv_img_set_zoom(lv_obj_t * obj, uint16_t zoom);
+
+/**
+ * Enable/disable anti-aliasing for the transformations (rotate, zoom) or not.
+ * The quality is better with anti-aliasing looks better but slower.
+ * @param obj pointer to an image object
+ * @param antialias true: anti-aliased; false: not anti-aliased
+ */
+void lv_img_set_antialias(lv_obj_t * obj, bool antialias);
+
+/**
+ * Set the image object size mode.
+ *
+ * @param obj pointer to an image object
+ * @param mode the new size mode.
+ */
+void lv_img_set_size_mode(lv_obj_t * obj, lv_img_size_mode_t mode);
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the source of the image
+ * @param obj pointer to an image object
+ * @return the image source (symbol, file name or ::lv-img_dsc_t for C arrays)
+ */
+const void * lv_img_get_src(lv_obj_t * obj);
+
+/**
+ * Get the offset's x attribute of the image object.
+ * @param img pointer to an image
+ * @return offset X value.
+ */
+lv_coord_t lv_img_get_offset_x(lv_obj_t * obj);
+
+/**
+ * Get the offset's y attribute of the image object.
+ * @param obj pointer to an image
+ * @return offset Y value.
+ */
+lv_coord_t lv_img_get_offset_y(lv_obj_t * obj);
+
+/**
+ * Get the rotation angle of the image.
+ * @param obj pointer to an image object
+ * @return rotation angle in 0.1 degrees (0..3600)
+ */
+uint16_t lv_img_get_angle(lv_obj_t * obj);
+
+/**
+ * Get the pivot (rotation center) of the image.
+ * @param img pointer to an image object
+ * @param pivot store the rotation center here
+ */
+void lv_img_get_pivot(lv_obj_t * obj, lv_point_t * pivot);
+
+/**
+ * Get the zoom factor of the image.
+ * @param obj pointer to an image object
+ * @return zoom factor (256: no zoom)
+ */
+uint16_t lv_img_get_zoom(lv_obj_t * obj);
+
+/**
+ * Get whether the transformations (rotate, zoom) are anti-aliased or not
+ * @param obj pointer to an image object
+ * @return true: anti-aliased; false: not anti-aliased
+ */
+bool lv_img_get_antialias(lv_obj_t * obj);
+
+/**
+ * Get the size mode of the image
+ * @param obj pointer to an image object
+ * @return element of @ref lv_img_size_mode_t
+ */
+lv_img_size_mode_t lv_img_get_size_mode(lv_obj_t * obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+/** Use this macro to declare an image in a C file*/
+#define LV_IMG_DECLARE(var_name) extern const lv_img_dsc_t var_name;
+
+#endif /*LV_USE_IMG*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_IMG_H*/
diff --git a/lib/lvgl/src/widgets/lv_label.c b/lib/lvgl/src/widgets/lv_label.c
new file mode 100644
index 00000000..f4fbe01a
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_label.c
@@ -0,0 +1,1274 @@
+/**
+ * @file lv_label.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_label.h"
+#if LV_USE_LABEL != 0
+#include "../core/lv_obj.h"
+#include "../misc/lv_assert.h"
+#include "../core/lv_group.h"
+#include "../draw/lv_draw.h"
+#include "../misc/lv_color.h"
+#include "../misc/lv_math.h"
+#include "../misc/lv_bidi.h"
+#include "../misc/lv_txt_ap.h"
+#include "../misc/lv_printf.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_label_class
+
+#define LV_LABEL_DEF_SCROLL_SPEED (lv_disp_get_dpi(lv_obj_get_disp(obj)) / 3)
+#define LV_LABEL_SCROLL_DELAY 300
+#define LV_LABEL_DOT_END_INV 0xFFFFFFFF
+#define LV_LABEL_HINT_HEIGHT_LIMIT 1024 /*Enable "hint" to buffer info about labels larger than this. (Speed up drawing)*/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_label_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_label_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_label_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void draw_main(lv_event_t * e);
+
+static void lv_label_refr_text(lv_obj_t * obj);
+static void lv_label_revert_dots(lv_obj_t * label);
+
+static bool lv_label_set_dot_tmp(lv_obj_t * label, char * data, uint32_t len);
+static char * lv_label_get_dot_tmp(lv_obj_t * label);
+static void lv_label_dot_tmp_free(lv_obj_t * label);
+static void set_ofs_x_anim(void * obj, int32_t v);
+static void set_ofs_y_anim(void * obj, int32_t v);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_label_class = {
+ .constructor_cb = lv_label_constructor,
+ .destructor_cb = lv_label_destructor,
+ .event_cb = lv_label_event,
+ .width_def = LV_SIZE_CONTENT,
+ .height_def = LV_SIZE_CONTENT,
+ .instance_size = sizeof(lv_label_t),
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_label_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
+ *====================*/
+
+void lv_label_set_text(lv_obj_t * obj, const char * text)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_label_t * label = (lv_label_t *)obj;
+
+ lv_obj_invalidate(obj);
+
+ /*If text is NULL then just refresh with the current text*/
+ if(text == NULL) text = label->text;
+
+ if(label->text == text && label->static_txt == 0) {
+ /*If set its own text then reallocate it (maybe its size changed)*/
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ /*Get the size of the text and process it*/
+ size_t len = _lv_txt_ap_calc_bytes_cnt(text);
+
+ label->text = lv_mem_realloc(label->text, len);
+ LV_ASSERT_MALLOC(label->text);
+ if(label->text == NULL) return;
+
+ _lv_txt_ap_proc(label->text, label->text);
+#else
+ label->text = lv_mem_realloc(label->text, strlen(label->text) + 1);
+#endif
+
+ LV_ASSERT_MALLOC(label->text);
+ if(label->text == NULL) return;
+ }
+ else {
+ /*Free the old text*/
+ if(label->text != NULL && label->static_txt == 0) {
+ lv_mem_free(label->text);
+ label->text = NULL;
+ }
+
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ /*Get the size of the text and process it*/
+ size_t len = _lv_txt_ap_calc_bytes_cnt(text);
+
+ label->text = lv_mem_alloc(len);
+ LV_ASSERT_MALLOC(label->text);
+ if(label->text == NULL) return;
+
+ _lv_txt_ap_proc(text, label->text);
+#else
+ /*Get the size of the text*/
+ size_t len = strlen(text) + 1;
+
+ /*Allocate space for the new text*/
+ label->text = lv_mem_alloc(len);
+ LV_ASSERT_MALLOC(label->text);
+ if(label->text == NULL) return;
+ strcpy(label->text, text);
+#endif
+
+ /*Now the text is dynamically allocated*/
+ label->static_txt = 0;
+ }
+
+ lv_label_refr_text(obj);
+}
+
+void lv_label_set_text_fmt(lv_obj_t * obj, const char * fmt, ...)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(fmt);
+
+ lv_obj_invalidate(obj);
+ lv_label_t * label = (lv_label_t *)obj;
+
+ /*If text is NULL then refresh*/
+ if(fmt == NULL) {
+ lv_label_refr_text(obj);
+ return;
+ }
+
+ if(label->text != NULL && label->static_txt == 0) {
+ lv_mem_free(label->text);
+ label->text = NULL;
+ }
+
+ va_list args;
+ va_start(args, fmt);
+ label->text = _lv_txt_set_text_vfmt(fmt, args);
+ va_end(args);
+ label->static_txt = 0; /*Now the text is dynamically allocated*/
+
+ lv_label_refr_text(obj);
+}
+
+void lv_label_set_text_static(lv_obj_t * obj, const char * text)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_label_t * label = (lv_label_t *)obj;
+
+ if(label->static_txt == 0 && label->text != NULL) {
+ lv_mem_free(label->text);
+ label->text = NULL;
+ }
+
+ if(text != NULL) {
+ label->static_txt = 1;
+ label->text = (char *)text;
+ }
+
+ lv_label_refr_text(obj);
+}
+
+void lv_label_set_long_mode(lv_obj_t * obj, lv_label_long_mode_t long_mode)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_label_t * label = (lv_label_t *)obj;
+
+ /*Delete the old animation (if exists)*/
+ lv_anim_del(obj, set_ofs_x_anim);
+ lv_anim_del(obj, set_ofs_y_anim);
+ label->offset.x = 0;
+ label->offset.y = 0;
+
+ if(long_mode == LV_LABEL_LONG_SCROLL || long_mode == LV_LABEL_LONG_SCROLL_CIRCULAR || long_mode == LV_LABEL_LONG_CLIP)
+ label->expand = 1;
+ else
+ label->expand = 0;
+
+ /*Restore the character under the dots*/
+ if(label->long_mode == LV_LABEL_LONG_DOT && label->dot_end != LV_LABEL_DOT_END_INV) {
+ lv_label_revert_dots(obj);
+ }
+
+ label->long_mode = long_mode;
+ lv_label_refr_text(obj);
+}
+
+void lv_label_set_recolor(lv_obj_t * obj, bool en)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_label_t * label = (lv_label_t *)obj;
+ if(label->recolor == en) return;
+
+ label->recolor = en == false ? 0 : 1;
+
+ /*Refresh the text because the potential color codes in text needs to be hidden or revealed*/
+ lv_label_refr_text(obj);
+}
+
+void lv_label_set_text_sel_start(lv_obj_t * obj, uint32_t index)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+#if LV_LABEL_TEXT_SELECTION
+ lv_label_t * label = (lv_label_t *)obj;
+ label->sel_start = index;
+ lv_obj_invalidate(obj);
+#else
+ LV_UNUSED(obj); /*Unused*/
+ LV_UNUSED(index); /*Unused*/
+#endif
+}
+
+void lv_label_set_text_sel_end(lv_obj_t * obj, uint32_t index)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+#if LV_LABEL_TEXT_SELECTION
+ lv_label_t * label = (lv_label_t *)obj;
+ label->sel_end = index;
+ lv_obj_invalidate(obj);
+#else
+ LV_UNUSED(obj); /*Unused*/
+ LV_UNUSED(index); /*Unused*/
+#endif
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+char * lv_label_get_text(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_label_t * label = (lv_label_t *)obj;
+ return label->text;
+}
+
+lv_label_long_mode_t lv_label_get_long_mode(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_label_t * label = (lv_label_t *)obj;
+ return label->long_mode;
+}
+
+bool lv_label_get_recolor(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_label_t * label = (lv_label_t *)obj;
+ return label->recolor == 0 ? false : true;
+}
+
+void lv_label_get_letter_pos(const lv_obj_t * obj, uint32_t char_id, lv_point_t * pos)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(pos);
+
+ lv_label_t * label = (lv_label_t *)obj;
+ const char * txt = lv_label_get_text(obj);
+ lv_text_align_t align = lv_obj_calculate_style_text_align(obj, LV_PART_MAIN, txt);
+
+ if(txt[0] == '\0') {
+ pos->y = 0;
+ switch(align) {
+ case LV_TEXT_ALIGN_LEFT:
+ pos->x = 0;
+ break;
+ case LV_TEXT_ALIGN_RIGHT:
+ pos->x = lv_obj_get_content_width(obj);
+ break;
+ case LV_TEXT_ALIGN_CENTER:
+ pos->x = lv_obj_get_content_width(obj) / 2;
+ break;
+ }
+ return;
+ }
+
+ lv_area_t txt_coords;
+ lv_obj_get_content_coords(obj, &txt_coords);
+
+ uint32_t line_start = 0;
+ uint32_t new_line_start = 0;
+ lv_coord_t max_w = lv_area_get_width(&txt_coords);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
+ lv_coord_t letter_height = lv_font_get_line_height(font);
+ lv_coord_t y = 0;
+ lv_text_flag_t flag = LV_TEXT_FLAG_NONE;
+
+ if(label->recolor != 0) flag |= LV_TEXT_FLAG_RECOLOR;
+ if(label->expand != 0) flag |= LV_TEXT_FLAG_EXPAND;
+ if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT && !obj->w_layout) flag |= LV_TEXT_FLAG_FIT;
+
+ uint32_t byte_id = _lv_txt_encoded_get_byte_id(txt, char_id);
+
+ /*Search the line of the index letter*/;
+ while(txt[new_line_start] != '\0') {
+ new_line_start += _lv_txt_get_next_line(&txt[line_start], font, letter_space, max_w, NULL, flag);
+ if(byte_id < new_line_start || txt[new_line_start] == '\0')
+ break; /*The line of 'index' letter begins at 'line_start'*/
+
+ y += letter_height + line_space;
+ line_start = new_line_start;
+ }
+
+ /*If the last character is line break then go to the next line*/
+ if(byte_id > 0) {
+ if((txt[byte_id - 1] == '\n' || txt[byte_id - 1] == '\r') && txt[byte_id] == '\0') {
+ y += letter_height + line_space;
+ line_start = byte_id;
+ }
+ }
+
+ const char * bidi_txt;
+ uint32_t visual_byte_pos;
+#if LV_USE_BIDI
+ lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
+ if(base_dir == LV_BASE_DIR_AUTO) base_dir = _lv_bidi_detect_base_dir(txt);
+
+ char * mutable_bidi_txt = NULL;
+ /*Handle Bidi*/
+ if(new_line_start == byte_id) {
+ visual_byte_pos = base_dir == LV_BASE_DIR_RTL ? 0 : byte_id - line_start;
+ bidi_txt = &txt[line_start];
+ }
+ else {
+ uint32_t line_char_id = _lv_txt_encoded_get_char_id(&txt[line_start], byte_id - line_start);
+
+ bool is_rtl;
+ uint32_t visual_char_pos = _lv_bidi_get_visual_pos(&txt[line_start], &mutable_bidi_txt, new_line_start - line_start,
+ base_dir, line_char_id, &is_rtl);
+ bidi_txt = mutable_bidi_txt;
+ if(is_rtl) visual_char_pos++;
+
+ visual_byte_pos = _lv_txt_encoded_get_byte_id(bidi_txt, visual_char_pos);
+ }
+#else
+ bidi_txt = &txt[line_start];
+ visual_byte_pos = byte_id - line_start;
+#endif
+
+ /*Calculate the x coordinate*/
+ lv_coord_t x = lv_txt_get_width(bidi_txt, visual_byte_pos, font, letter_space, flag);
+ if(char_id != line_start) x += letter_space;
+
+ if(align == LV_TEXT_ALIGN_CENTER) {
+ lv_coord_t line_w;
+ line_w = lv_txt_get_width(bidi_txt, new_line_start - line_start, font, letter_space, flag);
+ x += lv_area_get_width(&txt_coords) / 2 - line_w / 2;
+
+ }
+ else if(align == LV_TEXT_ALIGN_RIGHT) {
+ lv_coord_t line_w;
+ line_w = lv_txt_get_width(bidi_txt, new_line_start - line_start, font, letter_space, flag);
+
+ x += lv_area_get_width(&txt_coords) - line_w;
+ }
+ pos->x = x;
+ pos->y = y;
+
+#if LV_USE_BIDI
+ if(mutable_bidi_txt) lv_mem_buf_release(mutable_bidi_txt);
+#endif
+}
+
+uint32_t lv_label_get_letter_on(const lv_obj_t * obj, lv_point_t * pos_in)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(pos_in);
+ lv_label_t * label = (lv_label_t *)obj;
+
+ lv_point_t pos;
+ pos.x = pos_in->x - lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ pos.y = pos_in->y - lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+
+ lv_area_t txt_coords;
+ lv_obj_get_content_coords(obj, &txt_coords);
+ const char * txt = lv_label_get_text(obj);
+ uint32_t line_start = 0;
+ uint32_t new_line_start = 0;
+ lv_coord_t max_w = lv_area_get_width(&txt_coords);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
+ lv_coord_t letter_height = lv_font_get_line_height(font);
+ lv_coord_t y = 0;
+ lv_text_flag_t flag = LV_TEXT_FLAG_NONE;
+ uint32_t logical_pos;
+ char * bidi_txt;
+
+ if(label->recolor != 0) flag |= LV_TEXT_FLAG_RECOLOR;
+ if(label->expand != 0) flag |= LV_TEXT_FLAG_EXPAND;
+ if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT && !obj->w_layout) flag |= LV_TEXT_FLAG_FIT;
+
+ lv_text_align_t align = lv_obj_calculate_style_text_align(obj, LV_PART_MAIN, label->text);
+
+ /*Search the line of the index letter*/;
+ while(txt[line_start] != '\0') {
+ new_line_start += _lv_txt_get_next_line(&txt[line_start], font, letter_space, max_w, NULL, flag);
+
+ if(pos.y <= y + letter_height) {
+ /*The line is found (stored in 'line_start')*/
+ /*Include the NULL terminator in the last line*/
+ uint32_t tmp = new_line_start;
+ uint32_t letter;
+ letter = _lv_txt_encoded_prev(txt, &tmp);
+ if(letter != '\n' && txt[new_line_start] == '\0') new_line_start++;
+ break;
+ }
+ y += letter_height + line_space;
+
+ line_start = new_line_start;
+ }
+
+#if LV_USE_BIDI
+ bidi_txt = lv_mem_buf_get(new_line_start - line_start + 1);
+ uint32_t txt_len = new_line_start - line_start;
+ if(new_line_start > 0 && txt[new_line_start - 1] == '\0' && txt_len > 0) txt_len--;
+ _lv_bidi_process_paragraph(txt + line_start, bidi_txt, txt_len, lv_obj_get_style_base_dir(obj, LV_PART_MAIN), NULL, 0);
+#else
+ bidi_txt = (char *)txt + line_start;
+#endif
+
+ /*Calculate the x coordinate*/
+ lv_coord_t x = 0;
+ if(align == LV_TEXT_ALIGN_CENTER) {
+ lv_coord_t line_w;
+ line_w = lv_txt_get_width(bidi_txt, new_line_start - line_start, font, letter_space, flag);
+ x += lv_area_get_width(&txt_coords) / 2 - line_w / 2;
+ }
+ else if(align == LV_TEXT_ALIGN_RIGHT) {
+ lv_coord_t line_w;
+ line_w = lv_txt_get_width(bidi_txt, new_line_start - line_start, font, letter_space, flag);
+ x += lv_area_get_width(&txt_coords) - line_w;
+ }
+
+ lv_text_cmd_state_t cmd_state = LV_TEXT_CMD_STATE_WAIT;
+
+ uint32_t i = 0;
+ uint32_t i_act = i;
+
+ if(new_line_start > 0) {
+ while(i + line_start < new_line_start) {
+ /*Get the current letter and the next letter for kerning*/
+ /*Be careful 'i' already points to the next character*/
+ uint32_t letter;
+ uint32_t letter_next;
+ _lv_txt_encoded_letter_next_2(bidi_txt, &letter, &letter_next, &i);
+
+ /*Handle the recolor command*/
+ if((flag & LV_TEXT_FLAG_RECOLOR) != 0) {
+ if(_lv_txt_is_cmd(&cmd_state, bidi_txt[i]) != false) {
+ continue; /*Skip the letter if it is part of a command*/
+ }
+ }
+
+ lv_coord_t gw = lv_font_get_glyph_width(font, letter, letter_next);
+
+ /*Finish if the x position or the last char of the next line is reached*/
+ if(pos.x < x + gw || i + line_start == new_line_start || txt[i_act + line_start] == '\0') {
+ i = i_act;
+ break;
+ }
+ x += gw;
+ x += letter_space;
+ i_act = i;
+ }
+ }
+
+#if LV_USE_BIDI
+ /*Handle Bidi*/
+ uint32_t cid = _lv_txt_encoded_get_char_id(bidi_txt, i);
+ if(txt[line_start + i] == '\0') {
+ logical_pos = i;
+ }
+ else {
+ bool is_rtl;
+ logical_pos = _lv_bidi_get_logical_pos(&txt[line_start], NULL,
+ txt_len, lv_obj_get_style_base_dir(obj, LV_PART_MAIN), cid, &is_rtl);
+ if(is_rtl) logical_pos++;
+ }
+ lv_mem_buf_release(bidi_txt);
+#else
+ logical_pos = _lv_txt_encoded_get_char_id(bidi_txt, i);
+#endif
+
+ return logical_pos + _lv_txt_encoded_get_char_id(txt, line_start);
+}
+
+bool lv_label_is_char_under_pos(const lv_obj_t * obj, lv_point_t * pos)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(pos);
+
+ lv_area_t txt_coords;
+ lv_obj_get_content_coords(obj, &txt_coords);
+ const char * txt = lv_label_get_text(obj);
+ lv_label_t * label = (lv_label_t *)obj;
+ uint32_t line_start = 0;
+ uint32_t new_line_start = 0;
+ lv_coord_t max_w = lv_area_get_width(&txt_coords);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
+ lv_coord_t letter_height = lv_font_get_line_height(font);
+ lv_text_align_t align = lv_obj_calculate_style_text_align(obj, LV_PART_MAIN, label->text);
+
+ lv_coord_t y = 0;
+ lv_text_flag_t flag = LV_TEXT_FLAG_NONE;
+
+ if(label->recolor != 0) flag |= LV_TEXT_FLAG_RECOLOR;
+ if(label->expand != 0) flag |= LV_TEXT_FLAG_EXPAND;
+ if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT && !obj->w_layout) flag |= LV_TEXT_FLAG_FIT;
+
+ /*Search the line of the index letter*/;
+ while(txt[line_start] != '\0') {
+ new_line_start += _lv_txt_get_next_line(&txt[line_start], font, letter_space, max_w, NULL, flag);
+
+ if(pos->y <= y + letter_height) break; /*The line is found (stored in 'line_start')*/
+ y += letter_height + line_space;
+
+ line_start = new_line_start;
+ }
+
+ /*Calculate the x coordinate*/
+ lv_coord_t x = 0;
+ lv_coord_t last_x = 0;
+ if(align == LV_TEXT_ALIGN_CENTER) {
+ lv_coord_t line_w;
+ line_w = lv_txt_get_width(&txt[line_start], new_line_start - line_start, font, letter_space, flag);
+ x += lv_area_get_width(&txt_coords) / 2 - line_w / 2;
+ }
+ else if(align == LV_TEXT_ALIGN_RIGHT) {
+ lv_coord_t line_w;
+ line_w = lv_txt_get_width(&txt[line_start], new_line_start - line_start, font, letter_space, flag);
+ x += lv_area_get_width(&txt_coords) - line_w;
+ }
+
+ lv_text_cmd_state_t cmd_state = LV_TEXT_CMD_STATE_WAIT;
+
+ uint32_t i = line_start;
+ uint32_t i_current = i;
+ uint32_t letter = '\0';
+ uint32_t letter_next = '\0';
+
+ if(new_line_start > 0) {
+ while(i <= new_line_start - 1) {
+ /*Get the current letter and the next letter for kerning*/
+ /*Be careful 'i' already points to the next character*/
+ _lv_txt_encoded_letter_next_2(txt, &letter, &letter_next, &i);
+
+ /*Handle the recolor command*/
+ if((flag & LV_TEXT_FLAG_RECOLOR) != 0) {
+ if(_lv_txt_is_cmd(&cmd_state, txt[i]) != false) {
+ continue; /*Skip the letter if it is part of a command*/
+ }
+ }
+ last_x = x;
+ x += lv_font_get_glyph_width(font, letter, letter_next);
+ if(pos->x < x) {
+ i = i_current;
+ break;
+ }
+ x += letter_space;
+ i_current = i;
+ }
+ }
+
+ int32_t max_diff = lv_font_get_glyph_width(font, letter, letter_next) + letter_space + 1;
+ return (pos->x >= (last_x - letter_space) && pos->x <= (last_x + max_diff));
+}
+
+uint32_t lv_label_get_text_selection_start(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+#if LV_LABEL_TEXT_SELECTION
+ lv_label_t * label = (lv_label_t *)obj;
+ return label->sel_start;
+
+#else
+ LV_UNUSED(obj); /*Unused*/
+ return LV_LABEL_TEXT_SELECTION_OFF;
+#endif
+}
+
+uint32_t lv_label_get_text_selection_end(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+#if LV_LABEL_TEXT_SELECTION
+ lv_label_t * label = (lv_label_t *)obj;
+ return label->sel_end;
+#else
+ LV_UNUSED(obj); /*Unused*/
+ return LV_LABEL_TEXT_SELECTION_OFF;
+#endif
+}
+
+/*=====================
+ * Other functions
+ *====================*/
+
+void lv_label_ins_text(lv_obj_t * obj, uint32_t pos, const char * txt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(txt);
+
+ lv_label_t * label = (lv_label_t *)obj;
+
+ /*Can not append to static text*/
+ if(label->static_txt != 0) return;
+
+ lv_obj_invalidate(obj);
+
+ /*Allocate space for the new text*/
+ size_t old_len = strlen(label->text);
+ size_t ins_len = strlen(txt);
+ size_t new_len = ins_len + old_len;
+ label->text = lv_mem_realloc(label->text, new_len + 1);
+ LV_ASSERT_MALLOC(label->text);
+ if(label->text == NULL) return;
+
+ if(pos == LV_LABEL_POS_LAST) {
+ pos = _lv_txt_get_encoded_length(label->text);
+ }
+
+ _lv_txt_ins(label->text, pos, txt);
+ lv_label_set_text(obj, NULL);
+}
+
+void lv_label_cut_text(lv_obj_t * obj, uint32_t pos, uint32_t cnt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_label_t * label = (lv_label_t *)obj;
+
+ /*Can not append to static text*/
+ if(label->static_txt != 0) return;
+
+ lv_obj_invalidate(obj);
+
+ char * label_txt = lv_label_get_text(obj);
+ /*Delete the characters*/
+ _lv_txt_cut(label_txt, pos, cnt);
+
+ /*Refresh the label*/
+ lv_label_refr_text(obj);
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_label_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_label_t * label = (lv_label_t *)obj;
+
+ label->text = NULL;
+ label->static_txt = 0;
+ label->recolor = 0;
+ label->dot_end = LV_LABEL_DOT_END_INV;
+ label->long_mode = LV_LABEL_LONG_WRAP;
+ label->offset.x = 0;
+ label->offset.y = 0;
+
+#if LV_LABEL_LONG_TXT_HINT
+ label->hint.line_start = -1;
+ label->hint.coord_y = 0;
+ label->hint.y = 0;
+#endif
+
+#if LV_LABEL_TEXT_SELECTION
+ label->sel_start = LV_DRAW_LABEL_NO_TXT_SEL;
+ label->sel_end = LV_DRAW_LABEL_NO_TXT_SEL;
+#endif
+ label->dot.tmp_ptr = NULL;
+ label->dot_tmp_alloc = 0;
+
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE);
+ lv_label_set_long_mode(obj, LV_LABEL_LONG_WRAP);
+ lv_label_set_text(obj, "Text");
+
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_label_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ lv_label_t * label = (lv_label_t *)obj;
+
+ lv_label_dot_tmp_free(obj);
+ if(!label->static_txt) lv_mem_free(label->text);
+ label->text = NULL;
+}
+
+static void lv_label_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*/
+ 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_STYLE_CHANGED) {
+ /*Revert dots for proper refresh*/
+ lv_label_revert_dots(obj);
+ lv_label_refr_text(obj);
+ }
+ else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
+ /* Italic or other non-typical letters can be drawn of out of the object.
+ * It happens if box_w + ofs_x > adw_w in the glyph.
+ * To avoid this add some extra draw area.
+ * font_h / 4 is an empirical value. */
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+ lv_event_set_ext_draw_size(e, font_h / 4);
+ }
+ else if(code == LV_EVENT_SIZE_CHANGED) {
+ lv_label_revert_dots(obj);
+ lv_label_refr_text(obj);
+ }
+ else if(code == LV_EVENT_GET_SELF_SIZE) {
+ lv_point_t size;
+ lv_label_t * label = (lv_label_t *)obj;
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_text_flag_t flag = LV_TEXT_FLAG_NONE;
+ if(label->recolor != 0) flag |= LV_TEXT_FLAG_RECOLOR;
+ if(label->expand != 0) flag |= LV_TEXT_FLAG_EXPAND;
+
+ lv_coord_t w = lv_obj_get_content_width(obj);
+ if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT && !obj->w_layout) w = LV_COORD_MAX;
+ else w = lv_obj_get_content_width(obj);
+
+ lv_txt_get_size(&size, label->text, font, letter_space, line_space, w, flag);
+
+ lv_point_t * self_size = lv_event_get_param(e);
+ self_size->x = LV_MAX(self_size->x, size.x);
+ self_size->y = LV_MAX(self_size->y, size.y);
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ draw_main(e);
+ }
+}
+
+
+static void draw_main(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_label_t * label = (lv_label_t *)obj;
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ lv_area_t txt_coords;
+ lv_obj_get_content_coords(obj, &txt_coords);
+
+ lv_text_flag_t flag = LV_TEXT_FLAG_NONE;
+ if(label->recolor != 0) flag |= LV_TEXT_FLAG_RECOLOR;
+ if(label->expand != 0) flag |= LV_TEXT_FLAG_EXPAND;
+ if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT && !obj->w_layout) flag |= LV_TEXT_FLAG_FIT;
+
+ lv_draw_label_dsc_t label_draw_dsc;
+ lv_draw_label_dsc_init(&label_draw_dsc);
+
+ label_draw_dsc.ofs_x = label->offset.x;
+ label_draw_dsc.ofs_y = label->offset.y;
+
+ label_draw_dsc.flag = flag;
+ lv_obj_init_draw_label_dsc(obj, LV_PART_MAIN, &label_draw_dsc);
+ lv_bidi_calculate_align(&label_draw_dsc.align, &label_draw_dsc.bidi_dir, label->text);
+
+ label_draw_dsc.sel_start = lv_label_get_text_selection_start(obj);
+ label_draw_dsc.sel_end = lv_label_get_text_selection_end(obj);
+ if(label_draw_dsc.sel_start != LV_DRAW_LABEL_NO_TXT_SEL && label_draw_dsc.sel_end != LV_DRAW_LABEL_NO_TXT_SEL) {
+ label_draw_dsc.sel_color = lv_obj_get_style_text_color_filtered(obj, LV_PART_SELECTED);
+ label_draw_dsc.sel_bg_color = lv_obj_get_style_bg_color(obj, LV_PART_SELECTED);
+ }
+
+ /* In SCROLL and SCROLL_CIRCULAR mode the CENTER and RIGHT are pointless, so remove them.
+ * (In addition, they will create misalignment in this situation)*/
+ if((label->long_mode == LV_LABEL_LONG_SCROLL || label->long_mode == LV_LABEL_LONG_SCROLL_CIRCULAR) &&
+ (label_draw_dsc.align == LV_TEXT_ALIGN_CENTER || label_draw_dsc.align == LV_TEXT_ALIGN_RIGHT)) {
+ lv_point_t size;
+ lv_txt_get_size(&size, label->text, label_draw_dsc.font, label_draw_dsc.letter_space, label_draw_dsc.line_space,
+ LV_COORD_MAX, flag);
+ if(size.x > lv_area_get_width(&txt_coords)) {
+ label_draw_dsc.align = LV_TEXT_ALIGN_LEFT;
+ }
+ }
+#if LV_LABEL_LONG_TXT_HINT
+ lv_draw_label_hint_t * hint = &label->hint;
+ if(label->long_mode == LV_LABEL_LONG_SCROLL_CIRCULAR || lv_area_get_height(&txt_coords) < LV_LABEL_HINT_HEIGHT_LIMIT)
+ hint = NULL;
+
+#else
+ /*Just for compatibility*/
+ lv_draw_label_hint_t * hint = NULL;
+#endif
+
+ lv_area_t txt_clip;
+ bool is_common = _lv_area_intersect(&txt_clip, &txt_coords, draw_ctx->clip_area);
+ if(!is_common) return;
+
+ if(label->long_mode == LV_LABEL_LONG_WRAP) {
+ lv_coord_t s = lv_obj_get_scroll_top(obj);
+ lv_area_move(&txt_coords, 0, -s);
+ txt_coords.y2 = obj->coords.y2;
+ }
+ if(label->long_mode == LV_LABEL_LONG_SCROLL || label->long_mode == LV_LABEL_LONG_SCROLL_CIRCULAR) {
+ const lv_area_t * clip_area_ori = draw_ctx->clip_area;
+ draw_ctx->clip_area = &txt_clip;
+ lv_draw_label(draw_ctx, &label_draw_dsc, &txt_coords, label->text, hint);
+ draw_ctx->clip_area = clip_area_ori;
+ }
+ else {
+ lv_draw_label(draw_ctx, &label_draw_dsc, &txt_coords, label->text, hint);
+ }
+
+ const lv_area_t * clip_area_ori = draw_ctx->clip_area;
+ draw_ctx->clip_area = &txt_clip;
+
+ if(label->long_mode == LV_LABEL_LONG_SCROLL_CIRCULAR) {
+ lv_point_t size;
+ lv_txt_get_size(&size, label->text, label_draw_dsc.font, label_draw_dsc.letter_space, label_draw_dsc.line_space,
+ LV_COORD_MAX, flag);
+
+ /*Draw the text again on label to the original to make a circular effect */
+ if(size.x > lv_area_get_width(&txt_coords)) {
+ label_draw_dsc.ofs_x = label->offset.x + size.x +
+ lv_font_get_glyph_width(label_draw_dsc.font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT;
+ label_draw_dsc.ofs_y = label->offset.y;
+
+ lv_draw_label(draw_ctx, &label_draw_dsc, &txt_coords, label->text, hint);
+ }
+
+ /*Draw the text again below the original to make a circular effect */
+ if(size.y > lv_area_get_height(&txt_coords)) {
+ label_draw_dsc.ofs_x = label->offset.x;
+ label_draw_dsc.ofs_y = label->offset.y + size.y + lv_font_get_line_height(label_draw_dsc.font);
+
+ lv_draw_label(draw_ctx, &label_draw_dsc, &txt_coords, label->text, hint);
+ }
+ }
+
+ draw_ctx->clip_area = clip_area_ori;
+}
+
+/**
+ * Refresh the label with its text stored in its extended data
+ * @param label pointer to a label object
+ */
+static void lv_label_refr_text(lv_obj_t * obj)
+{
+ lv_label_t * label = (lv_label_t *)obj;
+ if(label->text == NULL) return;
+#if LV_LABEL_LONG_TXT_HINT
+ label->hint.line_start = -1; /*The hint is invalid if the text changes*/
+#endif
+
+ lv_area_t txt_coords;
+ lv_obj_get_content_coords(obj, &txt_coords);
+ lv_coord_t max_w = lv_area_get_width(&txt_coords);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);
+
+ /*Calc. the height and longest line*/
+ lv_point_t size;
+ lv_text_flag_t flag = LV_TEXT_FLAG_NONE;
+ if(label->recolor != 0) flag |= LV_TEXT_FLAG_RECOLOR;
+ if(label->expand != 0) flag |= LV_TEXT_FLAG_EXPAND;
+ if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT && !obj->w_layout) flag |= LV_TEXT_FLAG_FIT;
+
+ lv_txt_get_size(&size, label->text, font, letter_space, line_space, max_w, flag);
+
+ lv_obj_refresh_self_size(obj);
+
+ /*In scroll mode start an offset animation*/
+ if(label->long_mode == LV_LABEL_LONG_SCROLL) {
+ uint16_t anim_speed = lv_obj_get_style_anim_speed(obj, LV_PART_MAIN);
+ if(anim_speed == 0) anim_speed = LV_LABEL_DEF_SCROLL_SPEED;
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, obj);
+ lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
+ lv_anim_set_playback_delay(&a, LV_LABEL_SCROLL_DELAY);
+ lv_anim_set_repeat_delay(&a, a.playback_delay);
+
+ bool hor_anim = false;
+ if(size.x > lv_area_get_width(&txt_coords)) {
+#if LV_USE_BIDI
+ int32_t start, end;
+ lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
+
+ if(base_dir == LV_BASE_DIR_AUTO)
+ base_dir = _lv_bidi_detect_base_dir(label->text);
+
+ if(base_dir == LV_BASE_DIR_RTL) {
+ start = lv_area_get_width(&txt_coords) - size.x;
+ end = 0;
+ }
+ else {
+ start = 0;
+ end = lv_area_get_width(&txt_coords) - size.x;
+ }
+
+ lv_anim_set_values(&a, start, end);
+#else
+ lv_anim_set_values(&a, 0, lv_area_get_width(&txt_coords) - size.x);
+ lv_anim_set_exec_cb(&a, set_ofs_x_anim);
+#endif
+ lv_anim_set_exec_cb(&a, set_ofs_x_anim);
+
+ lv_anim_t * anim_cur = lv_anim_get(obj, set_ofs_x_anim);
+ int32_t act_time = 0;
+ bool playback_now = false;
+ if(anim_cur) {
+ act_time = anim_cur->act_time;
+ playback_now = anim_cur->playback_now;
+ }
+ if(act_time < a.time) {
+ a.act_time = act_time; /*To keep the old position*/
+ a.early_apply = 0;
+ if(playback_now) {
+ a.playback_now = 1;
+ /*Swap the start and end values*/
+ int32_t tmp;
+ tmp = a.start_value;
+ a.start_value = a.end_value;
+ a.end_value = tmp;
+ }
+ }
+
+ lv_anim_set_time(&a, lv_anim_speed_to_time(anim_speed, a.start_value, a.end_value));
+ lv_anim_set_playback_time(&a, a.time);
+ lv_anim_start(&a);
+ hor_anim = true;
+ }
+ else {
+ /*Delete the offset animation if not required*/
+ lv_anim_del(obj, set_ofs_x_anim);
+ label->offset.x = 0;
+ }
+
+ if(size.y > lv_area_get_height(&txt_coords) && hor_anim == false) {
+ lv_anim_set_values(&a, 0, lv_area_get_height(&txt_coords) - size.y - (lv_font_get_line_height(font)));
+ lv_anim_set_exec_cb(&a, set_ofs_y_anim);
+
+ lv_anim_t * anim_cur = lv_anim_get(obj, set_ofs_y_anim);
+ int32_t act_time = 0;
+ bool playback_now = false;
+ if(anim_cur) {
+ act_time = anim_cur->act_time;
+ playback_now = anim_cur->playback_now;
+ }
+ if(act_time < a.time) {
+ a.act_time = act_time; /*To keep the old position*/
+ a.early_apply = 0;
+ if(playback_now) {
+ a.playback_now = 1;
+ /*Swap the start and end values*/
+ int32_t tmp;
+ tmp = a.start_value;
+ a.start_value = a.end_value;
+ a.end_value = tmp;
+ }
+ }
+
+ lv_anim_set_time(&a, lv_anim_speed_to_time(anim_speed, a.start_value, a.end_value));
+ lv_anim_set_playback_time(&a, a.time);
+ lv_anim_start(&a);
+ }
+ else {
+ /*Delete the offset animation if not required*/
+ lv_anim_del(obj, set_ofs_y_anim);
+ label->offset.y = 0;
+ }
+ }
+ /*In roll inf. mode keep the size but start offset animations*/
+ else if(label->long_mode == LV_LABEL_LONG_SCROLL_CIRCULAR) {
+ const lv_anim_t * anim_template = lv_obj_get_style_anim(obj, LV_PART_MAIN);
+ uint16_t anim_speed = lv_obj_get_style_anim_speed(obj, LV_PART_MAIN);
+ if(anim_speed == 0) anim_speed = LV_LABEL_DEF_SCROLL_SPEED;
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, obj);
+ lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
+
+ bool hor_anim = false;
+ if(size.x > lv_area_get_width(&txt_coords)) {
+#if LV_USE_BIDI
+ int32_t start, end;
+ lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
+
+ if(base_dir == LV_BASE_DIR_AUTO)
+ base_dir = _lv_bidi_detect_base_dir(label->text);
+
+ if(base_dir == LV_BASE_DIR_RTL) {
+ start = -size.x - lv_font_get_glyph_width(font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT;
+ end = 0;
+ }
+ else {
+ start = 0;
+ end = -size.x - lv_font_get_glyph_width(font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT;
+ }
+
+ lv_anim_set_values(&a, start, end);
+#else
+ lv_anim_set_values(&a, 0, -size.x - lv_font_get_glyph_width(font, ' ', ' ') * LV_LABEL_WAIT_CHAR_COUNT);
+#endif
+ lv_anim_set_exec_cb(&a, set_ofs_x_anim);
+ lv_anim_set_time(&a, lv_anim_speed_to_time(anim_speed, a.start_value, a.end_value));
+
+ lv_anim_t * anim_cur = lv_anim_get(obj, set_ofs_x_anim);
+ int32_t act_time = anim_cur ? anim_cur->act_time : 0;
+
+ /*If a template animation exists, consider it's start delay and repeat delay*/
+ if(anim_template) {
+ a.act_time = anim_template->act_time;
+ a.repeat_delay = anim_template->repeat_delay;
+ }
+ else if(act_time < a.time) {
+ a.act_time = act_time; /*To keep the old position when the label text is updated mid-scrolling*/
+ a.early_apply = 0;
+ }
+
+ lv_anim_start(&a);
+ hor_anim = true;
+ }
+ else {
+ /*Delete the offset animation if not required*/
+ lv_anim_del(obj, set_ofs_x_anim);
+ label->offset.x = 0;
+ }
+
+ if(size.y > lv_area_get_height(&txt_coords) && hor_anim == false) {
+ lv_anim_set_values(&a, 0, -size.y - (lv_font_get_line_height(font)));
+ lv_anim_set_exec_cb(&a, set_ofs_y_anim);
+ lv_anim_set_time(&a, lv_anim_speed_to_time(anim_speed, a.start_value, a.end_value));
+
+ lv_anim_t * anim_cur = lv_anim_get(obj, set_ofs_y_anim);
+ int32_t act_time = anim_cur ? anim_cur->act_time : 0;
+
+ /*If a template animation exists, consider it's start delay and repeat delay*/
+ if(anim_template) {
+ a.act_time = anim_template->act_time;
+ a.repeat_delay = anim_template->repeat_delay;
+ }
+ else if(act_time < a.time) {
+ a.act_time = act_time; /*To keep the old position when the label text is updated mid-scrolling*/
+ a.early_apply = 0;
+ }
+
+ lv_anim_start(&a);
+ }
+ else {
+ /*Delete the offset animation if not required*/
+ lv_anim_del(obj, set_ofs_y_anim);
+ label->offset.y = 0;
+ }
+ }
+ else if(label->long_mode == LV_LABEL_LONG_DOT) {
+ if(size.y <= lv_area_get_height(&txt_coords)) { /*No dots are required, the text is short enough*/
+ label->dot_end = LV_LABEL_DOT_END_INV;
+ }
+ else if(size.y <= lv_font_get_line_height(font)) { /*No dots are required for one-line texts*/
+ label->dot_end = LV_LABEL_DOT_END_INV;
+ }
+ else if(_lv_txt_get_encoded_length(label->text) <= LV_LABEL_DOT_NUM) { /*Don't turn to dots all the characters*/
+ label->dot_end = LV_LABEL_DOT_END_INV;
+ }
+ else {
+ lv_point_t p;
+ lv_coord_t y_overed;
+ p.x = lv_area_get_width(&txt_coords) -
+ (lv_font_get_glyph_width(font, '.', '.') + letter_space) *
+ LV_LABEL_DOT_NUM; /*Shrink with dots*/
+ p.y = lv_area_get_height(&txt_coords);
+ y_overed = p.y %
+ (lv_font_get_line_height(font) + line_space); /*Round down to the last line*/
+ if(y_overed >= lv_font_get_line_height(font)) {
+ p.y -= y_overed;
+ p.y += lv_font_get_line_height(font);
+ }
+ else {
+ p.y -= y_overed;
+ p.y -= line_space;
+ }
+
+ uint32_t letter_id = lv_label_get_letter_on(obj, &p);
+
+ /*Be sure there is space for the dots*/
+ size_t txt_len = strlen(label->text);
+ uint32_t byte_id = _lv_txt_encoded_get_byte_id(label->text, letter_id);
+ while(byte_id + LV_LABEL_DOT_NUM > txt_len) {
+ _lv_txt_encoded_prev(label->text, &byte_id);
+ letter_id--;
+ }
+
+ /*Save letters under the dots and replace them with dots*/
+ uint32_t byte_id_ori = byte_id;
+ uint32_t i;
+ uint8_t len = 0;
+ for(i = 0; i <= LV_LABEL_DOT_NUM; i++) {
+ len += _lv_txt_encoded_size(&label->text[byte_id]);
+ _lv_txt_encoded_next(label->text, &byte_id);
+ if(len > LV_LABEL_DOT_NUM || byte_id > txt_len) {
+ break;
+ }
+ }
+
+ if(lv_label_set_dot_tmp(obj, &label->text[byte_id_ori], len)) {
+ for(i = 0; i < LV_LABEL_DOT_NUM; i++) {
+ label->text[byte_id_ori + i] = '.';
+ }
+ label->text[byte_id_ori + LV_LABEL_DOT_NUM] = '\0';
+ label->dot_end = letter_id + LV_LABEL_DOT_NUM;
+ }
+ }
+ }
+ else if(label->long_mode == LV_LABEL_LONG_CLIP) {
+ /*Do nothing*/
+ }
+
+ lv_obj_invalidate(obj);
+}
+
+
+static void lv_label_revert_dots(lv_obj_t * obj)
+{
+
+ lv_label_t * label = (lv_label_t *)obj;
+
+ if(label->long_mode != LV_LABEL_LONG_DOT) return;
+ if(label->dot_end == LV_LABEL_DOT_END_INV) return;
+ uint32_t letter_i = label->dot_end - LV_LABEL_DOT_NUM;
+ uint32_t byte_i = _lv_txt_encoded_get_byte_id(label->text, letter_i);
+
+ /*Restore the characters*/
+ uint8_t i = 0;
+ char * dot_tmp = lv_label_get_dot_tmp(obj);
+ while(label->text[byte_i + i] != '\0') {
+ label->text[byte_i + i] = dot_tmp[i];
+ i++;
+ }
+ label->text[byte_i + i] = dot_tmp[i];
+ lv_label_dot_tmp_free(obj);
+
+ label->dot_end = LV_LABEL_DOT_END_INV;
+}
+
+/**
+ * Store `len` characters from `data`. Allocates space if necessary.
+ *
+ * @param label pointer to label object
+ * @param len Number of characters to store.
+ * @return true on success.
+ */
+static bool lv_label_set_dot_tmp(lv_obj_t * obj, char * data, uint32_t len)
+{
+
+ lv_label_t * label = (lv_label_t *)obj;
+ lv_label_dot_tmp_free(obj); /*Deallocate any existing space*/
+ if(len > sizeof(char *)) {
+ /*Memory needs to be allocated. Allocates an additional byte
+ *for a NULL-terminator so it can be copied.*/
+ label->dot.tmp_ptr = lv_mem_alloc(len + 1);
+ if(label->dot.tmp_ptr == NULL) {
+ LV_LOG_ERROR("Failed to allocate memory for dot_tmp_ptr");
+ return false;
+ }
+ lv_memcpy(label->dot.tmp_ptr, data, len);
+ label->dot.tmp_ptr[len] = '\0';
+ label->dot_tmp_alloc = true;
+ }
+ else {
+ /*Characters can be directly stored in object*/
+ label->dot_tmp_alloc = false;
+ lv_memcpy(label->dot.tmp, data, len);
+ }
+ return true;
+}
+
+/**
+ * Get the stored dot_tmp characters
+ * @param label pointer to label object
+ * @return char pointer to a stored characters. Is *not* necessarily NULL-terminated.
+ */
+static char * lv_label_get_dot_tmp(lv_obj_t * obj)
+{
+ lv_label_t * label = (lv_label_t *)obj;
+ if(label->dot_tmp_alloc) {
+ return label->dot.tmp_ptr;
+ }
+ else {
+ return label->dot.tmp;
+ }
+}
+
+/**
+ * Free the dot_tmp_ptr field if it was previously allocated.
+ * Always clears the field
+ * @param label pointer to label object.
+ */
+static void lv_label_dot_tmp_free(lv_obj_t * obj)
+{
+ lv_label_t * label = (lv_label_t *)obj;
+ if(label->dot_tmp_alloc && label->dot.tmp_ptr) {
+ lv_mem_free(label->dot.tmp_ptr);
+ }
+ label->dot_tmp_alloc = false;
+ label->dot.tmp_ptr = NULL;
+}
+
+
+static void set_ofs_x_anim(void * obj, int32_t v)
+{
+ lv_label_t * label = (lv_label_t *)obj;
+ label->offset.x = v;
+ lv_obj_invalidate(obj);
+}
+
+static void set_ofs_y_anim(void * obj, int32_t v)
+{
+ lv_label_t * label = (lv_label_t *)obj;
+ label->offset.y = v;
+ lv_obj_invalidate(obj);
+}
+
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_label.h b/lib/lvgl/src/widgets/lv_label.h
new file mode 100644
index 00000000..b31a63e5
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_label.h
@@ -0,0 +1,246 @@
+/**
+ * @file lv_label.h
+ *
+ */
+
+#ifndef LV_LABEL_H
+#define LV_LABEL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_LABEL != 0
+
+#include <stdarg.h>
+#include "../core/lv_obj.h"
+#include "../font/lv_font.h"
+#include "../font/lv_symbol_def.h"
+#include "../misc/lv_txt.h"
+#include "../draw/lv_draw.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define LV_LABEL_WAIT_CHAR_COUNT 3
+#define LV_LABEL_DOT_NUM 3
+#define LV_LABEL_POS_LAST 0xFFFF
+#define LV_LABEL_TEXT_SELECTION_OFF LV_DRAW_LABEL_NO_TXT_SEL
+
+LV_EXPORT_CONST_INT(LV_LABEL_DOT_NUM);
+LV_EXPORT_CONST_INT(LV_LABEL_POS_LAST);
+LV_EXPORT_CONST_INT(LV_LABEL_TEXT_SELECTION_OFF);
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/** Long mode behaviors. Used in 'lv_label_ext_t'*/
+enum {
+ LV_LABEL_LONG_WRAP, /**< Keep the object width, wrap the too long lines and expand the object height*/
+ LV_LABEL_LONG_DOT, /**< Keep the size and write dots at the end if the text is too long*/
+ LV_LABEL_LONG_SCROLL, /**< Keep the size and roll the text back and forth*/
+ LV_LABEL_LONG_SCROLL_CIRCULAR, /**< Keep the size and roll the text circularly*/
+ LV_LABEL_LONG_CLIP, /**< Keep the size and clip the text out of it*/
+};
+typedef uint8_t lv_label_long_mode_t;
+
+typedef struct {
+ lv_obj_t obj;
+ char * text;
+ union {
+ char * tmp_ptr; /*Pointer to the allocated memory containing the character replaced by dots*/
+ char tmp[LV_LABEL_DOT_NUM + 1]; /*Directly store the characters if <=4 characters*/
+ } dot;
+ uint32_t dot_end; /*The real text length, used in dot mode*/
+
+#if LV_LABEL_LONG_TXT_HINT
+ lv_draw_label_hint_t hint;
+#endif
+
+#if LV_LABEL_TEXT_SELECTION
+ uint32_t sel_start;
+ uint32_t sel_end;
+#endif
+
+ lv_point_t offset; /*Text draw position offset*/
+ lv_label_long_mode_t long_mode : 3; /*Determine what to do with the long texts*/
+ uint8_t static_txt : 1; /*Flag to indicate the text is static*/
+ uint8_t recolor : 1; /*Enable in-line letter re-coloring*/
+ uint8_t expand : 1; /*Ignore real width (used by the library with LV_LABEL_LONG_SCROLL)*/
+ uint8_t dot_tmp_alloc : 1; /*1: dot is allocated, 0: dot directly holds up to 4 chars*/
+} lv_label_t;
+
+extern const lv_obj_class_t lv_label_class;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a label object
+ * @param parent pointer to an object, it will be the parent of the new label.
+ * @return pointer to the created button
+ */
+lv_obj_t * lv_label_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set a new text for a label. Memory will be allocated to store the text by the label.
+ * @param obj pointer to a label object
+ * @param text '\0' terminated character string. NULL to refresh with the current text.
+ */
+void lv_label_set_text(lv_obj_t * obj, const char * text);
+
+/**
+ * Set a new formatted text for a label. Memory will be allocated to store the text by the label.
+ * @param obj pointer to a label object
+ * @param fmt `printf`-like format
+ * @example lv_label_set_text_fmt(label1, "%d user", user_num);
+ */
+void lv_label_set_text_fmt(lv_obj_t * obj, const char * fmt, ...) LV_FORMAT_ATTRIBUTE(2, 3);
+
+/**
+ * Set a static text. It will not be saved by the label so the 'text' variable
+ * has to be 'alive' while the label exists.
+ * @param obj pointer to a label object
+ * @param text pointer to a text. NULL to refresh with the current text.
+ */
+void lv_label_set_text_static(lv_obj_t * obj, const char * text);
+
+/**
+ * Set the behavior of the label with longer text then the object size
+ * @param obj pointer to a label object
+ * @param long_mode the new mode from 'lv_label_long_mode' enum.
+ * In LV_LONG_WRAP/DOT/SCROLL/SCROLL_CIRC the size of the label should be set AFTER this function
+ */
+void lv_label_set_long_mode(lv_obj_t * obj, lv_label_long_mode_t long_mode);
+
+/**
+ * Enable the recoloring by in-line commands
+ * @param obj pointer to a label object
+ * @param en true: enable recoloring, false: disable
+ * @example "This is a #ff0000 red# word"
+ */
+void lv_label_set_recolor(lv_obj_t * obj, bool en);
+
+/**
+ * Set where text selection should start
+ * @param obj pointer to a label object
+ * @param index character index from where selection should start. `LV_LABEL_TEXT_SELECTION_OFF` for no selection
+ */
+void lv_label_set_text_sel_start(lv_obj_t * obj, uint32_t index);
+
+/**
+ * Set where text selection should end
+ * @param obj pointer to a label object
+ * @param index character index where selection should end. `LV_LABEL_TEXT_SELECTION_OFF` for no selection
+ */
+void lv_label_set_text_sel_end(lv_obj_t * obj, uint32_t index);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the text of a label
+ * @param obj pointer to a label object
+ * @return the text of the label
+ */
+char * lv_label_get_text(const lv_obj_t * obj);
+
+/**
+ * Get the long mode of a label
+ * @param obj pointer to a label object
+ * @return the current long mode
+ */
+lv_label_long_mode_t lv_label_get_long_mode(const lv_obj_t * obj);
+
+/**
+ * Get the recoloring attribute
+ * @param obj pointer to a label object
+ * @return true: recoloring is enabled, false: disable
+ */
+bool lv_label_get_recolor(const lv_obj_t * obj);
+
+/**
+ * Get the relative x and y coordinates of a letter
+ * @param obj pointer to a label object
+ * @param index index of the character [0 ... text length - 1].
+ * Expressed in character index, not byte index (different in UTF-8)
+ * @param pos store the result here (E.g. index = 0 gives 0;0 coordinates if the text if aligned to the left)
+ */
+void lv_label_get_letter_pos(const lv_obj_t * obj, uint32_t char_id, lv_point_t * pos);
+
+/**
+ * Get the index of letter on a relative point of a label.
+ * @param obj pointer to label object
+ * @param pos pointer to point with coordinates on a the label
+ * @return The index of the letter on the 'pos_p' point (E.g. on 0;0 is the 0. letter if aligned to the left)
+ * Expressed in character index and not byte index (different in UTF-8)
+ */
+uint32_t lv_label_get_letter_on(const lv_obj_t * obj, lv_point_t * pos_in);
+
+/**
+ * Check if a character is drawn under a point.
+ * @param obj pointer to a label object
+ * @param pos Point to check for character under
+ * @return whether a character is drawn under the point
+ */
+bool lv_label_is_char_under_pos(const lv_obj_t * obj, lv_point_t * pos);
+
+/**
+ * @brief Get the selection start index.
+ * @param obj pointer to a label object.
+ * @return selection start index. `LV_LABEL_TEXT_SELECTION_OFF` if nothing is selected.
+ */
+uint32_t lv_label_get_text_selection_start(const lv_obj_t * obj);
+
+/**
+ * @brief Get the selection end index.
+ * @param obj pointer to a label object.
+ * @return selection end index. `LV_LABEL_TXT_SEL_OFF` if nothing is selected.
+ */
+uint32_t lv_label_get_text_selection_end(const lv_obj_t * obj);
+
+/*=====================
+ * Other functions
+ *====================*/
+
+/**
+ * Insert a text to a label. The label text can not be static.
+ * @param obj pointer to a label object
+ * @param pos character index to insert. Expressed in character index and not byte index.
+ * 0: before first char. LV_LABEL_POS_LAST: after last char.
+ * @param txt pointer to the text to insert
+ */
+void lv_label_ins_text(lv_obj_t * obj, uint32_t pos, const char * txt);
+
+/**
+ * Delete characters from a label. The label text can not be static.
+ * @param obj pointer to a label object
+ * @param pos character index from where to cut. Expressed in character index and not byte index.
+ * 0: start in from of the first character
+ * @param cnt number of characters to cut
+ */
+void lv_label_cut_text(lv_obj_t * obj, uint32_t pos, uint32_t cnt);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_LABEL*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_LABEL_H*/
diff --git a/lib/lvgl/src/widgets/lv_line.c b/lib/lvgl/src/widgets/lv_line.c
new file mode 100644
index 00000000..df32bd05
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_line.c
@@ -0,0 +1,201 @@
+/**
+ * @file lv_line.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_line.h"
+
+#if LV_USE_LINE != 0
+#include "../misc/lv_assert.h"
+#include "../draw/lv_draw.h"
+#include "../misc/lv_math.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_line_class
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_line_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_line_event(const lv_obj_class_t * class_p, lv_event_t * e);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_line_class = {
+ .constructor_cb = lv_line_constructor,
+ .event_cb = lv_line_event,
+ .width_def = LV_SIZE_CONTENT,
+ .height_def = LV_SIZE_CONTENT,
+ .instance_size = sizeof(lv_line_t),
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_line_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
+ *====================*/
+
+void lv_line_set_points(lv_obj_t * obj, const lv_point_t points[], uint16_t point_num)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_line_t * line = (lv_line_t *)obj;
+ line->point_array = points;
+ line->point_num = point_num;
+
+ lv_obj_refresh_self_size(obj);
+
+ lv_obj_invalidate(obj);
+}
+
+void lv_line_set_y_invert(lv_obj_t * obj, bool en)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_line_t * line = (lv_line_t *)obj;
+ if(line->y_inv == en) return;
+
+ line->y_inv = en ? 1U : 0U;
+
+ lv_obj_invalidate(obj);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+bool lv_line_get_y_invert(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_line_t * line = (lv_line_t *)obj;
+
+ return line->y_inv == 1U;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_line_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_line_t * line = (lv_line_t *)obj;
+
+ line->point_num = 0;
+ line->point_array = NULL;
+ line->y_inv = 0;
+
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_line_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*/
+ 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_REFR_EXT_DRAW_SIZE) {
+ /*The corner of the skew lines is out of the intended area*/
+ lv_coord_t line_width = lv_obj_get_style_line_width(obj, LV_PART_MAIN);
+ lv_coord_t * s = lv_event_get_param(e);
+ if(*s < line_width) *s = line_width;
+ }
+ else if(code == LV_EVENT_GET_SELF_SIZE) {
+ lv_line_t * line = (lv_line_t *)obj;
+
+ if(line->point_num == 0 || line->point_array == NULL) return;
+
+ lv_point_t * p = lv_event_get_param(e);
+ lv_coord_t w = 0;
+ lv_coord_t h = 0;
+
+ uint16_t i;
+ for(i = 0; i < line->point_num; i++) {
+ w = LV_MAX(line->point_array[i].x, w);
+ h = LV_MAX(line->point_array[i].y, h);
+ }
+
+ lv_coord_t line_width = lv_obj_get_style_line_width(obj, LV_PART_MAIN);
+ w += line_width;
+ h += line_width;
+ p->x = w;
+ p->y = h;
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ lv_line_t * line = (lv_line_t *)obj;
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ if(line->point_num == 0 || line->point_array == NULL) return;
+
+ lv_area_t area;
+ lv_obj_get_coords(obj, &area);
+ lv_coord_t x_ofs = area.x1 - lv_obj_get_scroll_x(obj);
+ lv_coord_t y_ofs = area.y1 - lv_obj_get_scroll_y(obj);
+ lv_coord_t h = lv_obj_get_height(obj);
+
+ 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);
+
+ /*Read all points and draw the lines*/
+ uint16_t i;
+ for(i = 0; i < line->point_num - 1; i++) {
+ lv_point_t p1;
+ lv_point_t p2;
+ p1.x = line->point_array[i].x + x_ofs;
+ p2.x = line->point_array[i + 1].x + x_ofs;
+
+ if(line->y_inv == 0) {
+ p1.y = line->point_array[i].y + y_ofs;
+ p2.y = line->point_array[i + 1].y + y_ofs;
+ }
+ else {
+ p1.y = h - line->point_array[i].y + y_ofs;
+ p2.y = h - line->point_array[i + 1].y + y_ofs;
+ }
+ lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
+ line_dsc.round_start = 0; /*Draw the rounding only on the end points after the first line*/
+ }
+ }
+}
+#endif
diff --git a/lib/lvgl/src/widgets/lv_line.h b/lib/lvgl/src/widgets/lv_line.h
new file mode 100644
index 00000000..54fa248b
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_line.h
@@ -0,0 +1,93 @@
+/**
+ * @file lv_line.h
+ *
+ */
+
+#ifndef LV_LINE_H
+#define LV_LINE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_LINE != 0
+
+#include "../core/lv_obj.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/*Data of line*/
+typedef struct {
+ lv_obj_t obj;
+ const lv_point_t * point_array; /**< Pointer to an array with the points of the line*/
+ uint16_t point_num; /**< Number of points in 'point_array'*/
+ uint8_t y_inv : 1; /**< 1: y == 0 will be on the bottom*/
+} lv_line_t;
+
+extern const lv_obj_class_t lv_line_class;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a line object
+ * @param parent pointer to an object, it will be the parent of the new line
+ * @return pointer to the created line
+ */
+lv_obj_t * lv_line_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set an array of points. The line object will connect these points.
+ * @param obj pointer to a line object
+ * @param points an array of points. Only the address is saved, so the array needs to be alive while the line exists
+ * @param point_num number of points in 'point_a'
+ */
+void lv_line_set_points(lv_obj_t * obj, const lv_point_t points[], uint16_t point_num);
+
+/**
+ * Enable (or disable) the y coordinate inversion.
+ * If enabled then y will be subtracted from the height of the object,
+ * therefore the y = 0 coordinate will be on the bottom.
+ * @param obj pointer to a line object
+ * @param en true: enable the y inversion, false:disable the y inversion
+ */
+void lv_line_set_y_invert(lv_obj_t * obj, bool en);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the y inversion attribute
+ * @param obj pointer to a line object
+ * @return true: y inversion is enabled, false: disabled
+ */
+bool lv_line_get_y_invert(const lv_obj_t * obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_LINE*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_LINE_H*/
diff --git a/lib/lvgl/src/widgets/lv_objx_templ.c b/lib/lvgl/src/widgets/lv_objx_templ.c
new file mode 100644
index 00000000..91565466
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_objx_templ.c
@@ -0,0 +1,140 @@
+/**
+ * @file lv_templ.c
+ *
+ */
+
+/**
+ * TODO Remove these instructions
+ * Search and replace: templ -> object short name with lower case(e.g. btn, label etc)
+ * TEMPL -> object short name with upper case (e.g. BTN, LABEL etc.)
+ *
+ * You can remove the defined() clause from the #if statement below. This exists because
+ * LV_USE_TEMPL is not in lv_conf.h or lv_conf_template.h by default.
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+//#include "lv_templ.h" /*TODO uncomment this*/
+
+#if defined(LV_USE_TEMPL) && LV_USE_TEMPL != 0
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_templ_class
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_templ_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_templ_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_templ_event(const lv_obj_class_t * class_p, lv_event_t * e);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_templ_class = {
+ .constructor_cb = lv_templ_constructor,
+ .destructor_cb = lv_templ_destructor,
+ .event_cb = lv_templ_event,
+ .width_def = LV_DPI_DEF,
+ .height_def = LV_DPI_DEF,
+ .instance_size = sizeof(lv_templ_t),
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
+ .editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
+ .base_class = &lv_templ_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_templ_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/remove functions
+ *=====================*/
+
+/*
+ * New object specific "add" or "remove" functions come here
+ */
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/*
+ * New object specific "set" functions come here
+ */
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/*
+ * New object specific "get" functions come here
+ */
+
+/*=====================
+ * Other functions
+ *====================*/
+
+/*
+ * New object specific "other" functions come here
+ */
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_templ_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_templ_t * templ = (lv_templ_t *)obj;
+ /*Initialize the widget's data*/
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_templ_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ lv_templ_t * templ = (lv_templ_t *)obj;
+ /*Free the widget specific data*/
+}
+
+static void lv_templ_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*/
+ res = lv_obj_event_base(MY_CLASS, e);
+ if(res != LV_RES_OK) return;
+
+ /*Add the widget specific event handling here*/
+}
+
+#else /*Enable this file at the top*/
+
+/*This dummy typedef exists purely to silence -Wpedantic.*/
+typedef int keep_pedantic_happy;
+#endif
diff --git a/lib/lvgl/src/widgets/lv_objx_templ.h b/lib/lvgl/src/widgets/lv_objx_templ.h
new file mode 100644
index 00000000..9de5285b
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_objx_templ.h
@@ -0,0 +1,81 @@
+/**
+ * @file lv_templ.h
+ *
+ */
+
+/**
+ * TODO Remove these instructions
+ * Search and replace: templ -> object short name with lower case(e.g. btn, label etc)
+ * TEMPL -> object short name with upper case (e.g. BTN, LABEL etc.)
+ *
+ */
+
+#ifndef LV_TEMPL_H
+#define LV_TEMPL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_TEMPL != 0
+
+#include "../core/lv_obj.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+/*Data of template*/
+typedef struct {
+ lv_ANCESTOR_t ancestor; /*The ancestor widget, e.g. lv_slider_t slider*/
+ /*New data for this type*/
+} lv_templ_t;
+
+extern const lv_obj_class_t lv_templ_class;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a templ object
+ * @param parent pointer to an object, it will be the parent of the new templ
+ * @return pointer to the created bar
+ */
+lv_obj_t * lv_templ_create(lv_obj_t * parent);
+
+/*======================
+ * Add/remove functions
+ *=====================*/
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/*=====================
+ * Other functions
+ *====================*/
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_TEMPL*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_TEMPL_H*/
diff --git a/lib/lvgl/src/widgets/lv_roller.c b/lib/lvgl/src/widgets/lv_roller.c
new file mode 100644
index 00000000..fd9b3948
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_roller.c
@@ -0,0 +1,786 @@
+/**
+ * @file lv_roller.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_roller.h"
+#if LV_USE_ROLLER != 0
+
+#include "../misc/lv_assert.h"
+#include "../draw/lv_draw.h"
+#include "../core/lv_group.h"
+#include "../core/lv_indev.h"
+#include "../core/lv_indev_scroll.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_roller_class
+#define MY_CLASS_LABEL &lv_roller_label_class
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_roller_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_roller_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void lv_roller_label_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void draw_main(lv_event_t * e);
+static void draw_label(lv_event_t * e);
+static void get_sel_area(lv_obj_t * obj, lv_area_t * sel_area);
+static void refr_position(lv_obj_t * obj, lv_anim_enable_t animen);
+static lv_res_t release_handler(lv_obj_t * obj);
+static void inf_normalize(lv_obj_t * obj_scrl);
+static lv_obj_t * get_label(const lv_obj_t * obj);
+static lv_coord_t get_selected_label_width(const lv_obj_t * obj);
+static void scroll_anim_ready_cb(lv_anim_t * a);
+static void set_y_anim(void * obj, int32_t v);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_roller_class = {
+ .constructor_cb = lv_roller_constructor,
+ .event_cb = lv_roller_event,
+ .width_def = LV_SIZE_CONTENT,
+ .height_def = LV_DPI_DEF,
+ .instance_size = sizeof(lv_roller_t),
+ .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
+ .base_class = &lv_obj_class
+};
+
+const lv_obj_class_t lv_roller_label_class = {
+ .event_cb = lv_roller_label_event,
+ .instance_size = sizeof(lv_label_t),
+ .base_class = &lv_label_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**
+ * Create a roller object
+ * @param parent pointer to an object, it will be the parent of the new roller
+ * @return pointer to the created roller
+ */
+lv_obj_t * lv_roller_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 options on a roller
+ * @param roller pointer to roller object
+ * @param options a string with '\n' separated options. E.g. "One\nTwo\nThree"
+ * @param mode `LV_ROLLER_MODE_NORMAL` or `LV_ROLLER_MODE_INFINITE`
+ */
+void lv_roller_set_options(lv_obj_t * obj, const char * options, lv_roller_mode_t mode)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(options);
+
+ lv_roller_t * roller = (lv_roller_t *)obj;
+ lv_obj_t * label = get_label(obj);
+
+ roller->sel_opt_id = 0;
+ roller->sel_opt_id_ori = 0;
+
+ /*Count the '\n'-s to determine the number of options*/
+ roller->option_cnt = 0;
+ uint32_t cnt;
+ for(cnt = 0; options[cnt] != '\0'; cnt++) {
+ if(options[cnt] == '\n') roller->option_cnt++;
+ }
+ roller->option_cnt++; /*Last option has no `\n`*/
+
+ if(mode == LV_ROLLER_MODE_NORMAL) {
+ roller->mode = LV_ROLLER_MODE_NORMAL;
+ lv_label_set_text(label, options);
+ }
+ else {
+ roller->mode = LV_ROLLER_MODE_INFINITE;
+
+ size_t opt_len = strlen(options) + 1; /*+1 to add '\n' after option lists*/
+ char * opt_extra = lv_mem_buf_get(opt_len * LV_ROLLER_INF_PAGES);
+ uint8_t i;
+ for(i = 0; i < LV_ROLLER_INF_PAGES; i++) {
+ strcpy(&opt_extra[opt_len * i], options);
+ opt_extra[opt_len * (i + 1) - 1] = '\n';
+ }
+ opt_extra[opt_len * LV_ROLLER_INF_PAGES - 1] = '\0';
+ lv_label_set_text(label, opt_extra);
+ lv_mem_buf_release(opt_extra);
+
+ roller->sel_opt_id = ((LV_ROLLER_INF_PAGES / 2) + 0) * roller->option_cnt;
+
+ roller->option_cnt = roller->option_cnt * LV_ROLLER_INF_PAGES;
+ inf_normalize(obj);
+ }
+
+ roller->sel_opt_id_ori = roller->sel_opt_id;
+
+ /*If the selected text has larger font the label needs some extra draw padding to draw it.*/
+ lv_obj_refresh_ext_draw_size(label);
+
+}
+
+/**
+ * Set the selected option
+ * @param roller pointer to a roller object
+ * @param sel_opt id of the selected option (0 ... number of option - 1);
+ * @param anim_en LV_ANIM_ON: set with animation; LV_ANOM_OFF set immediately
+ */
+void lv_roller_set_selected(lv_obj_t * obj, uint16_t sel_opt, lv_anim_enable_t anim)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ /*Set the value even if it's the same as the current value because
+ *if moving to the next option with an animation which was just deleted in the PRESS Call the ancestor's event handler
+ *nothing will continue the animation.*/
+
+ lv_roller_t * roller = (lv_roller_t *)obj;
+
+ /*In infinite mode interpret the new ID relative to the currently visible "page"*/
+ if(roller->mode == LV_ROLLER_MODE_INFINITE) {
+ uint32_t real_option_cnt = roller->option_cnt / LV_ROLLER_INF_PAGES;
+ uint16_t current_page = roller->sel_opt_id / real_option_cnt;
+ /*Set by the user to e.g. 0, 1, 2, 3...
+ *Upscale the value to the current page*/
+ if(sel_opt < real_option_cnt) {
+ uint16_t act_opt = roller->sel_opt_id - current_page * real_option_cnt;
+ int32_t sel_opt_signed = sel_opt;
+ /*Huge jump? Probably from last to first or first to last option.*/
+ if(LV_ABS((int16_t)act_opt - sel_opt) > real_option_cnt / 2) {
+ if(act_opt > sel_opt) sel_opt_signed += real_option_cnt;
+ else sel_opt_signed -= real_option_cnt;
+ }
+ sel_opt = sel_opt_signed + real_option_cnt * current_page;
+ }
+ }
+
+ roller->sel_opt_id = sel_opt < roller->option_cnt ? sel_opt : roller->option_cnt - 1;
+ roller->sel_opt_id_ori = roller->sel_opt_id;
+
+ refr_position(obj, anim);
+}
+
+/**
+ * Set the height to show the given number of rows (options)
+ * @param roller pointer to a roller object
+ * @param row_cnt number of desired visible rows
+ */
+void lv_roller_set_visible_row_count(lv_obj_t * obj, uint8_t row_cnt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
+ lv_obj_set_height(obj, (lv_font_get_line_height(font) + line_space) * row_cnt + 2 * border_width);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the id of the selected option
+ * @param roller pointer to a roller object
+ * @return id of the selected option (0 ... number of option - 1);
+ */
+uint16_t lv_roller_get_selected(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_roller_t * roller = (lv_roller_t *)obj;
+ if(roller->mode == LV_ROLLER_MODE_INFINITE) {
+ uint16_t real_id_cnt = roller->option_cnt / LV_ROLLER_INF_PAGES;
+ return roller->sel_opt_id % real_id_cnt;
+ }
+ else {
+ return roller->sel_opt_id;
+ }
+}
+
+/**
+ * Get the current selected option as a string
+ * @param ddlist pointer to ddlist object
+ * @param buf pointer to an array to store the string
+ * @param buf_size size of `buf` in bytes. 0: to ignore it.
+ */
+void lv_roller_get_selected_str(const lv_obj_t * obj, char * buf, uint32_t buf_size)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_roller_t * roller = (lv_roller_t *)obj;
+ lv_obj_t * label = get_label(obj);
+ uint32_t i;
+ uint16_t line = 0;
+ const char * opt_txt = lv_label_get_text(label);
+ size_t txt_len = strlen(opt_txt);
+
+ for(i = 0; i < txt_len && line != roller->sel_opt_id; i++) {
+ if(opt_txt[i] == '\n') line++;
+ }
+
+ uint32_t c;
+ for(c = 0; i < txt_len && opt_txt[i] != '\n'; c++, i++) {
+ if(buf_size && c >= buf_size - 1) {
+ LV_LOG_WARN("lv_dropdown_get_selected_str: the buffer was too small");
+ break;
+ }
+ buf[c] = opt_txt[i];
+ }
+
+ buf[c] = '\0';
+}
+
+
+/**
+ * Get the options of a roller
+ * @param roller pointer to roller object
+ * @return the options separated by '\n'-s (E.g. "Option1\nOption2\nOption3")
+ */
+const char * lv_roller_get_options(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ return lv_label_get_text(get_label(obj));
+}
+
+
+/**
+ * Get the total number of options
+ * @param roller pointer to a roller object
+ * @return the total number of options
+ */
+uint16_t lv_roller_get_option_cnt(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_roller_t * roller = (lv_roller_t *)obj;
+ if(roller->mode == LV_ROLLER_MODE_INFINITE) {
+ return roller->option_cnt / LV_ROLLER_INF_PAGES;
+ }
+ else {
+ return roller->option_cnt;
+ }
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+
+static void lv_roller_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ lv_roller_t * roller = (lv_roller_t *)obj;
+
+ roller->mode = LV_ROLLER_MODE_NORMAL;
+ roller->option_cnt = 0;
+ roller->sel_opt_id = 0;
+ roller->sel_opt_id_ori = 0;
+
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN_VER);
+
+ LV_LOG_INFO("begin");
+ lv_obj_t * label = lv_obj_class_create_obj(&lv_roller_label_class, obj);
+ lv_obj_class_init_obj(label);
+ lv_roller_set_options(obj, "Option 1\nOption 2\nOption 3\nOption 4\nOption 5", LV_ROLLER_MODE_NORMAL);
+
+ LV_LOG_TRACE("finshed");
+}
+
+static void lv_roller_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*/
+ 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_roller_t * roller = (lv_roller_t *)obj;
+
+ if(code == LV_EVENT_GET_SELF_SIZE) {
+ lv_point_t * p = lv_event_get_param(e);
+ p->x = get_selected_label_width(obj);
+ }
+ else if(code == LV_EVENT_STYLE_CHANGED) {
+ lv_obj_t * label = get_label(obj);
+ /*Be sure the label's style is updated before processing the roller*/
+ if(label) lv_event_send(label, LV_EVENT_STYLE_CHANGED, NULL);
+ lv_obj_refresh_self_size(obj);
+ refr_position(obj, LV_ANIM_OFF);
+ }
+ else if(code == LV_EVENT_SIZE_CHANGED) {
+ refr_position(obj, LV_ANIM_OFF);
+ }
+ else if(code == LV_EVENT_PRESSED) {
+ roller->moved = 0;
+ lv_anim_del(get_label(obj), set_y_anim);
+ }
+ else if(code == LV_EVENT_PRESSING) {
+ lv_indev_t * indev = lv_indev_get_act();
+ lv_point_t p;
+ lv_indev_get_vect(indev, &p);
+ if(p.y) {
+ lv_obj_t * label = get_label(obj);
+ lv_obj_set_y(label, lv_obj_get_y(label) + p.y);
+ roller->moved = 1;
+ }
+ }
+ else if(code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
+ release_handler(obj);
+ }
+ else if(code == LV_EVENT_FOCUSED) {
+ lv_group_t * g = lv_obj_get_group(obj);
+ bool editing = lv_group_get_editing(g);
+ lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
+
+ /*Encoders need special handling*/
+ if(indev_type == LV_INDEV_TYPE_ENCODER) {
+ /*In navigate mode revert the original value*/
+ if(!editing) {
+ if(roller->sel_opt_id != roller->sel_opt_id_ori) {
+ roller->sel_opt_id = roller->sel_opt_id_ori;
+ refr_position(obj, LV_ANIM_ON);
+ }
+ }
+ /*Save the current state when entered to edit mode*/
+ else {
+ roller->sel_opt_id_ori = roller->sel_opt_id;
+ }
+ }
+ else {
+ roller->sel_opt_id_ori = roller->sel_opt_id; /*Save the current value. Used to revert this state if
+ ENTER won't be pressed*/
+ }
+ }
+ else if(code == LV_EVENT_DEFOCUSED) {
+ /*Revert the original state*/
+ if(roller->sel_opt_id != roller->sel_opt_id_ori) {
+ roller->sel_opt_id = roller->sel_opt_id_ori;
+ refr_position(obj, LV_ANIM_ON);
+ }
+ }
+ else if(code == LV_EVENT_KEY) {
+ char c = *((char *)lv_event_get_param(e));
+ if(c == LV_KEY_RIGHT || c == LV_KEY_DOWN) {
+ if(roller->sel_opt_id + 1 < roller->option_cnt) {
+ uint16_t ori_id = roller->sel_opt_id_ori; /*lv_roller_set_selected will overwrite this*/
+ lv_roller_set_selected(obj, roller->sel_opt_id + 1, LV_ANIM_ON);
+ roller->sel_opt_id_ori = ori_id;
+ }
+ }
+ else if(c == LV_KEY_LEFT || c == LV_KEY_UP) {
+ if(roller->sel_opt_id > 0) {
+ uint16_t ori_id = roller->sel_opt_id_ori; /*lv_roller_set_selected will overwrite this*/
+
+ lv_roller_set_selected(obj, roller->sel_opt_id - 1, LV_ANIM_ON);
+ roller->sel_opt_id_ori = ori_id;
+ }
+ }
+ }
+ else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
+ lv_obj_t * label = get_label(obj);
+ lv_obj_refresh_ext_draw_size(label);
+ }
+ else if(code == LV_EVENT_DRAW_MAIN || code == LV_EVENT_DRAW_POST) {
+ draw_main(e);
+ }
+}
+
+static void lv_roller_label_event(const lv_obj_class_t * class_p, lv_event_t * e)
+{
+ LV_UNUSED(class_p);
+
+ lv_res_t res;
+
+ lv_event_code_t code = lv_event_get_code(e);
+ /*LV_EVENT_DRAW_MAIN will be called in the draw function*/
+ if(code != LV_EVENT_DRAW_MAIN) {
+ /* Call the ancestor's event handler */
+ res = lv_obj_event_base(MY_CLASS_LABEL, e);
+ if(res != LV_RES_OK) return;
+ }
+
+ lv_obj_t * label = lv_event_get_target(e);
+ if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
+ /*If the selected text has a larger font it needs some extra space to draw it*/
+ lv_coord_t * s = lv_event_get_param(e);
+ lv_obj_t * obj = lv_obj_get_parent(label);
+ lv_coord_t sel_w = get_selected_label_width(obj);
+ lv_coord_t label_w = lv_obj_get_width(label);
+ *s = LV_MAX(*s, sel_w - label_w);
+ }
+ else if(code == LV_EVENT_SIZE_CHANGED) {
+ refr_position(lv_obj_get_parent(label), LV_ANIM_OFF);
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ draw_label(e);
+ }
+}
+
+
+static void draw_main(lv_event_t * e)
+{
+ 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) {
+ /*Draw the selected rectangle*/
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+ lv_area_t sel_area;
+ get_sel_area(obj, &sel_area);
+ lv_draw_rect_dsc_t sel_dsc;
+ lv_draw_rect_dsc_init(&sel_dsc);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_SELECTED, &sel_dsc);
+ lv_draw_rect(draw_ctx, &sel_dsc, &sel_area);
+ }
+ /*Post draw when the children are drawn*/
+ else if(code == LV_EVENT_DRAW_POST) {
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ lv_draw_label_dsc_t label_dsc;
+ lv_draw_label_dsc_init(&label_dsc);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_SELECTED, &label_dsc);
+
+ /*Redraw the text on the selected area*/
+ lv_area_t sel_area;
+ get_sel_area(obj, &sel_area);
+ lv_area_t mask_sel;
+ bool area_ok;
+ area_ok = _lv_area_intersect(&mask_sel, draw_ctx->clip_area, &sel_area);
+ if(area_ok) {
+ lv_obj_t * label = get_label(obj);
+
+ /*Get the size of the "selected text"*/
+ lv_point_t res_p;
+ lv_txt_get_size(&res_p, lv_label_get_text(label), label_dsc.font, label_dsc.letter_space, label_dsc.line_space,
+ lv_obj_get_width(obj), LV_TEXT_FLAG_EXPAND);
+
+ /*Move the selected label proportionally with the background label*/
+ lv_coord_t roller_h = lv_obj_get_height(obj);
+ int32_t label_y_prop = label->coords.y1 - (roller_h / 2 +
+ obj->coords.y1); /*label offset from the middle line of the roller*/
+ label_y_prop = (label_y_prop * 16384) / lv_obj_get_height(
+ label); /*Proportional position from the middle line (upscaled by << 14)*/
+
+ /*Apply a correction with different line heights*/
+ const lv_font_t * normal_label_font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t corr = (label_dsc.font->line_height - normal_label_font->line_height) / 2;
+
+ /*Apply the proportional position to the selected text*/
+ res_p.y -= corr;
+ int32_t label_sel_y = roller_h / 2 + obj->coords.y1;
+ label_sel_y += (label_y_prop * res_p.y) >> 14;
+ label_sel_y -= corr;
+
+ 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 pright = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+
+ /*Draw the selected text*/
+ lv_area_t label_sel_area;
+ label_sel_area.x1 = obj->coords.x1 + pleft + bwidth;
+ label_sel_area.y1 = label_sel_y;
+ label_sel_area.x2 = obj->coords.x2 - pright - bwidth;
+ label_sel_area.y2 = label_sel_area.y1 + res_p.y;
+
+ label_dsc.flag |= LV_TEXT_FLAG_EXPAND;
+ const lv_area_t * clip_area_ori = draw_ctx->clip_area;
+ draw_ctx->clip_area = &mask_sel;
+ lv_draw_label(draw_ctx, &label_dsc, &label_sel_area, lv_label_get_text(label), NULL);
+ draw_ctx->clip_area = clip_area_ori;
+ }
+ }
+}
+
+static void draw_label(lv_event_t * e)
+{
+ /* Split the drawing of the label into an upper (above the selected area)
+ * and a lower (below the selected area)*/
+ lv_obj_t * label_obj = lv_event_get_target(e);
+ lv_obj_t * roller = lv_obj_get_parent(label_obj);
+ lv_draw_label_dsc_t label_draw_dsc;
+ lv_draw_label_dsc_init(&label_draw_dsc);
+ lv_obj_init_draw_label_dsc(roller, LV_PART_MAIN, &label_draw_dsc);
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ /*If the roller has shadow or outline it has some ext. draw size
+ *therefore the label can overflow the roller's boundaries.
+ *To solve this limit the clip area to the "plain" roller.*/
+ const lv_area_t * clip_area_ori = draw_ctx->clip_area;
+ lv_area_t roller_clip_area;
+ if(!_lv_area_intersect(&roller_clip_area, draw_ctx->clip_area, &roller->coords)) return;
+ draw_ctx->clip_area = &roller_clip_area;
+
+ lv_area_t sel_area;
+ get_sel_area(roller, &sel_area);
+
+ lv_area_t clip2;
+ clip2.x1 = label_obj->coords.x1;
+ clip2.y1 = label_obj->coords.y1;
+ clip2.x2 = label_obj->coords.x2;
+ clip2.y2 = sel_area.y1;
+ if(_lv_area_intersect(&clip2, draw_ctx->clip_area, &clip2)) {
+ const lv_area_t * clip_area_ori2 = draw_ctx->clip_area;
+ draw_ctx->clip_area = &clip2;
+ lv_draw_label(draw_ctx, &label_draw_dsc, &label_obj->coords, lv_label_get_text(label_obj), NULL);
+ draw_ctx->clip_area = clip_area_ori2;
+ }
+
+ clip2.x1 = label_obj->coords.x1;
+ clip2.y1 = sel_area.y2;
+ clip2.x2 = label_obj->coords.x2;
+ clip2.y2 = label_obj->coords.y2;
+ if(_lv_area_intersect(&clip2, draw_ctx->clip_area, &clip2)) {
+ const lv_area_t * clip_area_ori2 = draw_ctx->clip_area;
+ draw_ctx->clip_area = &clip2;
+ lv_draw_label(draw_ctx, &label_draw_dsc, &label_obj->coords, lv_label_get_text(label_obj), NULL);
+ draw_ctx->clip_area = clip_area_ori2;
+ }
+
+ draw_ctx->clip_area = clip_area_ori;
+}
+
+static void get_sel_area(lv_obj_t * obj, lv_area_t * sel_area)
+{
+
+ const lv_font_t * font_main = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ const lv_font_t * font_sel = lv_obj_get_style_text_font(obj, LV_PART_SELECTED);
+ lv_coord_t font_main_h = lv_font_get_line_height(font_main);
+ lv_coord_t font_sel_h = lv_font_get_line_height(font_sel);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t d = (font_sel_h + font_main_h) / 2 + line_space;
+ sel_area->y1 = obj->coords.y1 + lv_obj_get_height(obj) / 2 - d / 2;
+ sel_area->y2 = sel_area->y1 + d;
+ lv_area_t roller_coords;
+ lv_obj_get_coords(obj, &roller_coords);
+
+ sel_area->x1 = roller_coords.x1;
+ sel_area->x2 = roller_coords.x2;
+
+}
+
+/**
+ * Refresh the position of the roller. It uses the id stored in: roller->ddlist.selected_option_id
+ * @param roller pointer to a roller object
+ * @param anim_en LV_ANIM_ON: refresh with animation; LV_ANOM_OFF: without animation
+ */
+static void refr_position(lv_obj_t * obj, lv_anim_enable_t anim_en)
+{
+ lv_obj_t * label = get_label(obj);
+ if(label == NULL) return;
+
+ lv_text_align_t align = lv_obj_calculate_style_text_align(label, LV_PART_MAIN, lv_label_get_text(label));
+
+ switch(align) {
+ case LV_TEXT_ALIGN_CENTER:
+ lv_obj_set_x(label, (lv_obj_get_content_width(obj) - lv_obj_get_width(label)) / 2);
+ break;
+ case LV_TEXT_ALIGN_RIGHT:
+ lv_obj_set_x(label, lv_obj_get_content_width(obj) - lv_obj_get_width(label));
+ break;
+ case LV_TEXT_ALIGN_LEFT:
+ lv_obj_set_x(label, 0);
+ break;
+ }
+
+ lv_roller_t * roller = (lv_roller_t *)obj;
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+ lv_coord_t h = lv_obj_get_content_height(obj);
+ uint16_t anim_time = lv_obj_get_style_anim_time(obj, LV_PART_MAIN);
+
+ /*Normally the animation's `end_cb` sets correct position of the roller if infinite.
+ *But without animations do it manually*/
+ if(anim_en == LV_ANIM_OFF || anim_time == 0) {
+ inf_normalize(obj);
+ }
+
+ int32_t id = roller->sel_opt_id;
+ lv_coord_t sel_y1 = id * (font_h + line_space);
+ lv_coord_t mid_y1 = h / 2 - font_h / 2;
+
+ lv_coord_t new_y = mid_y1 - sel_y1;
+
+ if(anim_en == LV_ANIM_OFF || anim_time == 0) {
+ lv_anim_del(label, set_y_anim);
+ lv_obj_set_y(label, new_y);
+ }
+ else {
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, label);
+ lv_anim_set_exec_cb(&a, set_y_anim);
+ lv_anim_set_values(&a, lv_obj_get_y(label), new_y);
+ lv_anim_set_time(&a, anim_time);
+ lv_anim_set_ready_cb(&a, scroll_anim_ready_cb);
+ lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
+ lv_anim_start(&a);
+ }
+}
+
+static lv_res_t release_handler(lv_obj_t * obj)
+{
+ lv_obj_t * label = get_label(obj);
+ if(label == NULL) return LV_RES_OK;
+
+ lv_indev_t * indev = lv_indev_get_act();
+ lv_roller_t * roller = (lv_roller_t *)obj;
+
+ /*Leave edit mode once a new option is selected*/
+ lv_indev_type_t indev_type = lv_indev_get_type(indev);
+ if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) {
+ roller->sel_opt_id_ori = roller->sel_opt_id;
+
+ if(indev_type == LV_INDEV_TYPE_ENCODER) {
+ lv_group_t * g = lv_obj_get_group(obj);
+ if(lv_group_get_editing(g)) {
+ lv_group_set_editing(g, false);
+ }
+ }
+ }
+
+ if(lv_indev_get_type(indev) == LV_INDEV_TYPE_POINTER || lv_indev_get_type(indev) == LV_INDEV_TYPE_BUTTON) {
+ /*Search the clicked option (For KEYPAD and ENCODER the new value should be already set)*/
+ int16_t new_opt = -1;
+ if(roller->moved == 0) {
+ new_opt = 0;
+ lv_point_t p;
+ lv_indev_get_point(indev, &p);
+ p.y -= label->coords.y1;
+ p.x -= label->coords.x1;
+ uint32_t letter_i;
+ letter_i = lv_label_get_letter_on(label, &p);
+
+ const char * txt = lv_label_get_text(label);
+ uint32_t i = 0;
+ uint32_t i_prev = 0;
+
+ uint32_t letter_cnt = 0;
+ for(letter_cnt = 0; letter_cnt < letter_i; letter_cnt++) {
+ uint32_t letter = _lv_txt_encoded_next(txt, &i);
+ /*Count he lines to reach the clicked letter. But ignore the last '\n' because it
+ * still belongs to the clicked line*/
+ if(letter == '\n' && i_prev != letter_i) new_opt++;
+ i_prev = i;
+ }
+ }
+ else {
+ /*If dragged then align the list to have an element in the middle*/
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+
+ lv_coord_t label_unit = font_h + line_space;
+ lv_coord_t mid = obj->coords.y1 + (obj->coords.y2 - obj->coords.y1) / 2;
+ lv_coord_t label_y1 = label->coords.y1 + lv_indev_scroll_throw_predict(indev, LV_DIR_VER);
+ int32_t id = (mid - label_y1) / label_unit;
+
+ if(id < 0) id = 0;
+ if(id >= roller->option_cnt) id = roller->option_cnt - 1;
+
+ new_opt = id;
+ }
+
+ if(new_opt >= 0) {
+ lv_roller_set_selected(obj, new_opt, LV_ANIM_ON);
+ }
+ }
+
+ uint32_t id = roller->sel_opt_id; /*Just to use uint32_t in event data*/
+ lv_res_t res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, &id);
+ return res;
+}
+
+/**
+ * Set the middle page for the roller if infinite is enabled
+ * @param roller pointer to a roller object
+ */
+static void inf_normalize(lv_obj_t * obj)
+{
+ lv_roller_t * roller = (lv_roller_t *)obj;
+
+ if(roller->mode == LV_ROLLER_MODE_INFINITE) {
+ uint16_t real_id_cnt = roller->option_cnt / LV_ROLLER_INF_PAGES;
+ roller->sel_opt_id = roller->sel_opt_id % real_id_cnt;
+ roller->sel_opt_id += (LV_ROLLER_INF_PAGES / 2) * real_id_cnt; /*Select the middle page*/
+
+ roller->sel_opt_id_ori = roller->sel_opt_id % real_id_cnt;
+ roller->sel_opt_id_ori += (LV_ROLLER_INF_PAGES / 2) * real_id_cnt; /*Select the middle page*/
+
+ /*Move to the new id*/
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+ lv_coord_t h = lv_obj_get_content_height(obj);
+
+ lv_obj_t * label = get_label(obj);
+
+
+ lv_coord_t sel_y1 = roller->sel_opt_id * (font_h + line_space);
+ lv_coord_t mid_y1 = h / 2 - font_h / 2;
+ lv_coord_t new_y = mid_y1 - sel_y1;
+ lv_obj_set_y(label, new_y);
+ }
+}
+
+static lv_obj_t * get_label(const lv_obj_t * obj)
+{
+ return lv_obj_get_child(obj, 0);
+}
+
+
+static lv_coord_t get_selected_label_width(const lv_obj_t * obj)
+{
+ lv_obj_t * label = get_label(obj);
+ if(label == NULL) return 0;
+
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_SELECTED);
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_SELECTED);
+ const char * txt = lv_label_get_text(label);
+ lv_point_t size;
+ lv_txt_get_size(&size, txt, font, letter_space, 0, LV_COORD_MAX, LV_TEXT_FLAG_NONE);
+ return size.x;
+}
+
+static void scroll_anim_ready_cb(lv_anim_t * a)
+{
+ lv_obj_t * obj = lv_obj_get_parent(a->var); /*The label is animated*/
+ inf_normalize(obj);
+}
+
+
+static void set_y_anim(void * obj, int32_t v)
+{
+ lv_obj_set_y(obj, v);
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_roller.h b/lib/lvgl/src/widgets/lv_roller.h
new file mode 100644
index 00000000..14411dea
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_roller.h
@@ -0,0 +1,138 @@
+/**
+ * @file lv_roller.h
+ *
+ */
+
+#ifndef LV_ROLLER_H
+#define LV_ROLLER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_ROLLER != 0
+
+/*Testing of dependencies*/
+#if LV_USE_LABEL == 0
+#error "lv_roller: lv_label is required. Enable it in lv_conf.h (LV_USE_ROLLER 1)"
+#endif
+
+#include "../core/lv_obj.h"
+#include "lv_label.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/** Roller mode.*/
+enum {
+ LV_ROLLER_MODE_NORMAL, /**< Normal mode (roller ends at the end of the options).*/
+ LV_ROLLER_MODE_INFINITE, /**< Infinite mode (roller can be scrolled forever).*/
+};
+
+typedef uint8_t lv_roller_mode_t;
+
+typedef struct {
+ lv_obj_t obj;
+ uint16_t option_cnt; /**< Number of options*/
+ uint16_t sel_opt_id; /**< Index of the current option*/
+ uint16_t sel_opt_id_ori; /**< Store the original index on focus*/
+ lv_roller_mode_t mode : 1;
+ uint32_t moved : 1;
+} lv_roller_t;
+
+extern const lv_obj_class_t lv_roller_class;
+
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a roller object
+ * @param parent pointer to an object, it will be the parent of the new roller.
+ * @return pointer to the created roller
+ */
+lv_obj_t * lv_roller_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set the options on a roller
+ * @param obj pointer to roller object
+ * @param options a string with '\n' separated options. E.g. "One\nTwo\nThree"
+ * @param mode `LV_ROLLER_MODE_NORMAL` or `LV_ROLLER_MODE_INFINITE`
+ */
+void lv_roller_set_options(lv_obj_t * obj, const char * options, lv_roller_mode_t mode);
+
+/**
+ * Set the selected option
+ * @param obj pointer to a roller object
+ * @param sel_opt index of the selected option (0 ... number of option - 1);
+ * @param anim_en LV_ANIM_ON: set with animation; LV_ANOM_OFF set immediately
+ */
+void lv_roller_set_selected(lv_obj_t * obj, uint16_t sel_opt, lv_anim_enable_t anim);
+
+/**
+ * Set the height to show the given number of rows (options)
+ * @param obj pointer to a roller object
+ * @param row_cnt number of desired visible rows
+ */
+void lv_roller_set_visible_row_count(lv_obj_t * obj, uint8_t row_cnt);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the index of the selected option
+ * @param obj pointer to a roller object
+ * @return index of the selected option (0 ... number of option - 1);
+ */
+uint16_t lv_roller_get_selected(const lv_obj_t * obj);
+
+/**
+ * Get the current selected option as a string.
+ * @param obj pointer to ddlist object
+ * @param buf pointer to an array to store the string
+ * @param buf_size size of `buf` in bytes. 0: to ignore it.
+ */
+void lv_roller_get_selected_str(const lv_obj_t * obj, char * buf, uint32_t buf_size);
+
+
+/**
+ * Get the options of a roller
+ * @param obj pointer to roller object
+ * @return the options separated by '\n'-s (E.g. "Option1\nOption2\nOption3")
+ */
+const char * lv_roller_get_options(const lv_obj_t * obj);
+
+/**
+ * Get the total number of options
+ * @param obj pointer to a roller object
+ * @return the total number of options
+ */
+uint16_t lv_roller_get_option_cnt(const lv_obj_t * obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_ROLLER*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_ROLLER_H*/
diff --git a/lib/lvgl/src/widgets/lv_slider.c b/lib/lvgl/src/widgets/lv_slider.c
new file mode 100644
index 00000000..3f85efc2
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_slider.c
@@ -0,0 +1,443 @@
+/**
+ * @file lv_slider.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_slider.h"
+#if LV_USE_SLIDER != 0
+
+#include "../misc/lv_assert.h"
+#include "../core/lv_group.h"
+#include "../core/lv_indev.h"
+#include "../draw/lv_draw.h"
+#include "../misc/lv_math.h"
+#include "../core/lv_disp.h"
+#include "lv_img.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_slider_class
+
+#define LV_SLIDER_KNOB_COORD(is_rtl, area) (is_rtl ? area.x1 : area.x2)
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_slider_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_slider_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void position_knob(lv_obj_t * obj, lv_area_t * knob_area, const lv_coord_t knob_size, const bool hor);
+static void draw_knob(lv_event_t * e);
+static bool is_slider_horizontal(lv_obj_t * obj);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_slider_class = {
+ .constructor_cb = lv_slider_constructor,
+ .event_cb = lv_slider_event,
+ .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
+ .instance_size = sizeof(lv_slider_t),
+ .base_class = &lv_bar_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_slider_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;
+}
+
+bool lv_slider_is_dragged(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_slider_t * slider = (lv_slider_t *)obj;
+
+ return slider->dragging ? true : false;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_slider_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ lv_slider_t * slider = (lv_slider_t *)obj;
+
+ /*Initialize the allocated 'slider'*/
+ slider->value_to_set = NULL;
+ slider->dragging = 0U;
+ slider->left_knob_focus = 0U;
+
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN);
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_set_ext_click_area(obj, LV_DPX(8));
+}
+
+static void lv_slider_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*/
+ 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_slider_t * slider = (lv_slider_t *)obj;
+ lv_slider_mode_t type = lv_slider_get_mode(obj);
+
+ /*Advanced hit testing: react only on dragging the knob(s)*/
+ if(code == LV_EVENT_HIT_TEST) {
+ lv_hit_test_info_t * info = lv_event_get_param(e);
+ lv_coord_t ext_click_area = obj->spec_attr ? obj->spec_attr->ext_click_pad : 0;
+
+ /*Ordinary slider: was the knob area hit?*/
+ lv_area_t a;
+ lv_area_copy(&a, &slider->right_knob_area);
+ lv_area_increase(&a, ext_click_area, ext_click_area);
+ info->res = _lv_area_is_point_on(&a, info->point, 0);
+
+ /*There's still a chance that there is a hit if there is another knob*/
+ if((info->res == false) && (type == LV_SLIDER_MODE_RANGE)) {
+ lv_area_copy(&a, &slider->left_knob_area);
+ lv_area_increase(&a, ext_click_area, ext_click_area);
+ info->res = _lv_area_is_point_on(&a, info->point, 0);
+ }
+ }
+ else if(code == LV_EVENT_PRESSED) {
+ lv_obj_invalidate(obj);
+
+ lv_point_t p;
+ slider->dragging = true;
+ if(type == LV_SLIDER_MODE_NORMAL || type == LV_SLIDER_MODE_SYMMETRICAL) {
+ slider->value_to_set = &slider->bar.cur_value;
+ }
+ else if(type == LV_SLIDER_MODE_RANGE) {
+ lv_indev_get_point(lv_indev_get_act(), &p);
+ bool hor = lv_obj_get_width(obj) >= lv_obj_get_height(obj);
+ lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
+
+ lv_coord_t dist_left, dist_right;
+ if(hor) {
+ if((base_dir != LV_BASE_DIR_RTL && p.x > slider->right_knob_area.x2) || (base_dir == LV_BASE_DIR_RTL &&
+ p.x < slider->right_knob_area.x1)) {
+ slider->value_to_set = &slider->bar.cur_value;
+ }
+ else if((base_dir != LV_BASE_DIR_RTL && p.x < slider->left_knob_area.x1) || (base_dir == LV_BASE_DIR_RTL &&
+ p.x > slider->left_knob_area.x2)) {
+ slider->value_to_set = &slider->bar.start_value;
+ }
+ else {
+ /*Calculate the distance from each knob*/
+ dist_left = LV_ABS((slider->left_knob_area.x1 + (slider->left_knob_area.x2 - slider->left_knob_area.x1) / 2) - p.x);
+ dist_right = LV_ABS((slider->right_knob_area.x1 + (slider->right_knob_area.x2 - slider->right_knob_area.x1) / 2) - p.x);
+
+ /*Use whichever one is closer*/
+ if(dist_right < dist_left) {
+ slider->value_to_set = &slider->bar.cur_value;
+ slider->left_knob_focus = 0;
+ }
+ else {
+ slider->value_to_set = &slider->bar.start_value;
+ slider->left_knob_focus = 1;
+ }
+ }
+ }
+ else {
+ if(p.y < slider->right_knob_area.y1) {
+ slider->value_to_set = &slider->bar.cur_value;
+ }
+ else if(p.y > slider->left_knob_area.y2) {
+ slider->value_to_set = &slider->bar.start_value;
+ }
+ else {
+ /*Calculate the distance from each knob*/
+ dist_left = LV_ABS((slider->left_knob_area.y1 + (slider->left_knob_area.y2 - slider->left_knob_area.y1) / 2) - p.y);
+ dist_right = LV_ABS((slider->right_knob_area.y1 + (slider->right_knob_area.y2 - slider->right_knob_area.y1) / 2) - p.y);
+
+ /*Use whichever one is closer*/
+ if(dist_right < dist_left) {
+ slider->value_to_set = &slider->bar.cur_value;
+ slider->left_knob_focus = 0;
+ }
+ else {
+ slider->value_to_set = &slider->bar.start_value;
+ slider->left_knob_focus = 1;
+ }
+ }
+ }
+ }
+ }
+ else if(code == LV_EVENT_PRESSING && slider->value_to_set != NULL) {
+ lv_indev_t * indev = lv_indev_get_act();
+ if(lv_indev_get_type(indev) != LV_INDEV_TYPE_POINTER) return;
+
+ lv_point_t p;
+ lv_indev_get_point(indev, &p);
+ int32_t new_value = 0;
+
+ const int32_t range = slider->bar.max_value - slider->bar.min_value;
+ if(is_slider_horizontal(obj)) {
+ const lv_coord_t bg_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ const lv_coord_t bg_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+ const lv_coord_t w = lv_obj_get_width(obj);
+ const lv_coord_t indic_w = w - bg_left - bg_right;
+
+ if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) == LV_BASE_DIR_RTL) {
+ /*Make the point relative to the indicator*/
+ new_value = (obj->coords.x2 - bg_right) - p.x;
+ }
+ else {
+ /*Make the point relative to the indicator*/
+ new_value = p.x - (obj->coords.x1 + bg_left);
+ }
+ new_value = (new_value * range + indic_w / 2) / indic_w;
+ new_value += slider->bar.min_value;
+ }
+ else {
+ const lv_coord_t bg_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ const lv_coord_t bg_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
+ const lv_coord_t h = lv_obj_get_height(obj);
+ const lv_coord_t indic_h = h - bg_bottom - bg_top;
+
+ /*Make the point relative to the indicator*/
+ new_value = p.y - (obj->coords.y2 + bg_bottom);
+ new_value = (-new_value * range + indic_h / 2) / indic_h;
+ new_value += slider->bar.min_value;
+ }
+
+ int32_t real_max_value = slider->bar.max_value;
+ int32_t real_min_value = slider->bar.min_value;
+ /*Figure out the min. and max. for this mode*/
+ if(slider->value_to_set == &slider->bar.start_value) {
+ real_max_value = slider->bar.cur_value;
+ }
+ else {
+ real_min_value = slider->bar.start_value;
+ }
+
+ new_value = LV_CLAMP(real_min_value, new_value, real_max_value);
+ if(*slider->value_to_set != new_value) {
+ *slider->value_to_set = new_value;
+ lv_obj_invalidate(obj);
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
+ if(res != LV_RES_OK) return;
+ }
+
+ }
+ else if(code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
+ slider->dragging = false;
+ slider->value_to_set = NULL;
+
+ lv_obj_invalidate(obj);
+
+ /*Leave edit mode if released. (No need to wait for LONG_PRESS)*/
+ lv_group_t * g = lv_obj_get_group(obj);
+ bool editing = lv_group_get_editing(g);
+ lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
+ if(indev_type == LV_INDEV_TYPE_ENCODER) {
+ if(editing) {
+ if(lv_slider_get_mode(obj) == LV_SLIDER_MODE_RANGE) {
+ if(slider->left_knob_focus == 0) slider->left_knob_focus = 1;
+ else {
+ slider->left_knob_focus = 0;
+ lv_group_set_editing(g, false);
+ }
+ }
+ else {
+ lv_group_set_editing(g, false);
+ }
+ }
+ }
+
+ }
+ else if(code == LV_EVENT_FOCUSED) {
+ lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
+ if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) {
+ slider->left_knob_focus = 0;
+ }
+ }
+ else if(code == LV_EVENT_SIZE_CHANGED) {
+ lv_obj_refresh_ext_draw_size(obj);
+ }
+ else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
+ lv_coord_t knob_left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
+ lv_coord_t knob_right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
+ lv_coord_t knob_top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
+ lv_coord_t knob_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
+
+ /*The smaller size is the knob diameter*/
+ lv_coord_t zoom = lv_obj_get_style_transform_zoom(obj, LV_PART_KNOB);
+ lv_coord_t trans_w = lv_obj_get_style_transform_width(obj, LV_PART_KNOB);
+ lv_coord_t trans_h = lv_obj_get_style_transform_height(obj, LV_PART_KNOB);
+ lv_coord_t knob_size = LV_MIN(lv_obj_get_width(obj) + 2 * trans_w, lv_obj_get_height(obj) + 2 * trans_h) >> 1;
+ knob_size = (knob_size * zoom) >> 8;
+ knob_size += LV_MAX(LV_MAX(knob_left, knob_right), LV_MAX(knob_bottom, knob_top));
+ knob_size += 2; /*For rounding error*/
+ knob_size += lv_obj_calculate_ext_draw_size(obj, LV_PART_KNOB);
+
+ /*Indic. size is handled by bar*/
+ lv_coord_t * s = lv_event_get_param(e);
+ *s = LV_MAX(*s, knob_size);
+
+ }
+ else if(code == LV_EVENT_KEY) {
+ char c = *((char *)lv_event_get_param(e));
+
+ if(c == LV_KEY_RIGHT || c == LV_KEY_UP) {
+ if(!slider->left_knob_focus) lv_slider_set_value(obj, lv_slider_get_value(obj) + 1, LV_ANIM_ON);
+ else lv_slider_set_left_value(obj, lv_slider_get_left_value(obj) + 1, LV_ANIM_ON);
+ }
+ else if(c == LV_KEY_LEFT || c == LV_KEY_DOWN) {
+ if(!slider->left_knob_focus) lv_slider_set_value(obj, lv_slider_get_value(obj) - 1, LV_ANIM_ON);
+ else lv_slider_set_left_value(obj, lv_slider_get_left_value(obj) - 1, LV_ANIM_ON);
+ }
+ else {
+ return;
+ }
+
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
+ if(res != LV_RES_OK) return;
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ draw_knob(e);
+ }
+}
+
+static void draw_knob(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_slider_t * slider = (lv_slider_t *)obj;
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ const bool is_rtl = LV_BASE_DIR_RTL == lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
+ const bool is_horizontal = is_slider_horizontal(obj);
+
+ lv_area_t knob_area;
+ lv_coord_t knob_size;
+ bool is_symmetrical = false;
+ if(slider->bar.mode == LV_BAR_MODE_SYMMETRICAL && slider->bar.min_value < 0 &&
+ slider->bar.max_value > 0) is_symmetrical = true;
+
+ if(is_horizontal) {
+ knob_size = lv_obj_get_height(obj);
+ if(is_symmetrical && slider->bar.cur_value < 0) knob_area.x1 = slider->bar.indic_area.x1;
+ else knob_area.x1 = LV_SLIDER_KNOB_COORD(is_rtl, slider->bar.indic_area);
+ }
+ else {
+ knob_size = lv_obj_get_width(obj);
+ if(is_symmetrical && slider->bar.cur_value < 0) knob_area.y1 = slider->bar.indic_area.y2;
+ else knob_area.y1 = slider->bar.indic_area.y1;
+ }
+
+ lv_draw_rect_dsc_t knob_rect_dsc;
+ lv_draw_rect_dsc_init(&knob_rect_dsc);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_KNOB, &knob_rect_dsc);
+ /* Update knob area with knob style */
+ position_knob(obj, &knob_area, knob_size, is_horizontal);
+ /* Update right knob area with calculated knob area */
+ lv_area_copy(&slider->right_knob_area, &knob_area);
+
+ 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_KNOB;
+ part_draw_dsc.class_p = MY_CLASS;
+ part_draw_dsc.type = LV_SLIDER_DRAW_PART_KNOB;
+ part_draw_dsc.id = 0;
+ part_draw_dsc.draw_area = &slider->right_knob_area;
+ part_draw_dsc.rect_dsc = &knob_rect_dsc;
+
+ if(lv_slider_get_mode(obj) != LV_SLIDER_MODE_RANGE) {
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+ lv_draw_rect(draw_ctx, &knob_rect_dsc, &slider->right_knob_area);
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+ }
+ else {
+ /*Save the draw part_draw_dsc. because it can be modified in the event*/
+ lv_draw_rect_dsc_t knob_rect_dsc_tmp;
+ lv_memcpy(&knob_rect_dsc_tmp, &knob_rect_dsc, sizeof(lv_draw_rect_dsc_t));
+ /* Draw the right knob */
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+ lv_draw_rect(draw_ctx, &knob_rect_dsc, &slider->right_knob_area);
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+
+ /*Calculate the second knob area*/
+ if(is_horizontal) {
+ /*use !is_rtl to get the other knob*/
+ knob_area.x1 = LV_SLIDER_KNOB_COORD(!is_rtl, slider->bar.indic_area);
+ }
+ else {
+ knob_area.y1 = slider->bar.indic_area.y2;
+ }
+ position_knob(obj, &knob_area, knob_size, is_horizontal);
+ lv_area_copy(&slider->left_knob_area, &knob_area);
+
+ lv_memcpy(&knob_rect_dsc, &knob_rect_dsc_tmp, sizeof(lv_draw_rect_dsc_t));
+ part_draw_dsc.type = LV_SLIDER_DRAW_PART_KNOB_LEFT;
+ part_draw_dsc.draw_area = &slider->left_knob_area;
+ part_draw_dsc.rect_dsc = &knob_rect_dsc;
+ part_draw_dsc.id = 1;
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+ lv_draw_rect(draw_ctx, &knob_rect_dsc, &slider->left_knob_area);
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+ }
+}
+
+static void position_knob(lv_obj_t * obj, lv_area_t * knob_area, const lv_coord_t knob_size, const bool hor)
+{
+ if(hor) {
+ knob_area->x1 -= (knob_size >> 1);
+ knob_area->x2 = knob_area->x1 + knob_size - 1;
+ knob_area->y1 = obj->coords.y1;
+ knob_area->y2 = obj->coords.y2;
+ }
+ else {
+ knob_area->y1 -= (knob_size >> 1);
+ knob_area->y2 = knob_area->y1 + knob_size - 1;
+ knob_area->x1 = obj->coords.x1;
+ knob_area->x2 = obj->coords.x2;
+ }
+
+ lv_coord_t knob_left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
+ lv_coord_t knob_right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
+ lv_coord_t knob_top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
+ lv_coord_t knob_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
+
+ lv_coord_t transf_w = lv_obj_get_style_transform_width(obj, LV_PART_KNOB);
+ lv_coord_t transf_h = lv_obj_get_style_transform_height(obj, LV_PART_KNOB);
+
+ /*Apply the paddings on the knob area*/
+ knob_area->x1 -= knob_left + transf_w;
+ knob_area->x2 += knob_right + transf_w;
+ knob_area->y1 -= knob_top + transf_h;
+ knob_area->y2 += knob_bottom + transf_h;
+}
+
+static bool is_slider_horizontal(lv_obj_t * obj)
+{
+ return lv_obj_get_width(obj) >= lv_obj_get_height(obj);
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_slider.h b/lib/lvgl/src/widgets/lv_slider.h
new file mode 100644
index 00000000..386950ce
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_slider.h
@@ -0,0 +1,195 @@
+/**
+ * @file lv_slider.h
+ *
+ */
+
+#ifndef LV_SLIDER_H
+#define LV_SLIDER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_SLIDER != 0
+
+/*Testing of dependencies*/
+#if LV_USE_BAR == 0
+#error "lv_slider: lv_bar is required. Enable it in lv_conf.h (LV_USE_BAR 1)"
+#endif
+
+#include "../core/lv_obj.h"
+#include "lv_bar.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+enum {
+ LV_SLIDER_MODE_NORMAL = LV_BAR_MODE_NORMAL,
+ LV_SLIDER_MODE_SYMMETRICAL = LV_BAR_MODE_SYMMETRICAL,
+ LV_SLIDER_MODE_RANGE = LV_BAR_MODE_RANGE
+};
+typedef uint8_t lv_slider_mode_t;
+
+typedef struct {
+ lv_bar_t bar; /*Add the ancestor's type first*/
+ lv_area_t left_knob_area;
+ lv_area_t right_knob_area;
+ int32_t * value_to_set; /*Which bar value to set*/
+ uint8_t dragging : 1; /*1: the slider is being dragged*/
+ uint8_t left_knob_focus : 1; /*1: with encoder now the right knob can be adjusted*/
+} lv_slider_t;
+
+extern const lv_obj_class_t lv_slider_class;
+
+/**
+ * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_slider_class`
+ * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END`
+ */
+typedef enum {
+ LV_SLIDER_DRAW_PART_KNOB, /**< The main (right) knob's rectangle*/
+ LV_SLIDER_DRAW_PART_KNOB_LEFT, /**< The left knob's rectangle*/
+} lv_slider_draw_part_type_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a slider object
+ * @param parent pointer to an object, it will be the parent of the new slider.
+ * @return pointer to the created slider
+ */
+lv_obj_t * lv_slider_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set a new value on the slider
+ * @param obj pointer to a slider object
+ * @param value the new value
+ * @param anim LV_ANIM_ON: set the value with an animation; LV_ANIM_OFF: change the value immediately
+ */
+static inline void lv_slider_set_value(lv_obj_t * obj, int32_t value, lv_anim_enable_t anim)
+{
+ lv_bar_set_value(obj, value, anim);
+}
+
+/**
+ * Set a new value for the left knob of a slider
+ * @param obj pointer to a slider object
+ * @param value new value
+ * @param anim LV_ANIM_ON: set the value with an animation; LV_ANIM_OFF: change the value immediately
+ */
+static inline void lv_slider_set_left_value(lv_obj_t * obj, int32_t value, lv_anim_enable_t anim)
+{
+ lv_bar_set_start_value(obj, value, anim);
+}
+
+/**
+ * Set minimum and the maximum values of a bar
+ * @param obj pointer to the slider object
+ * @param min minimum value
+ * @param max maximum value
+ */
+static inline void lv_slider_set_range(lv_obj_t * obj, int32_t min, int32_t max)
+{
+ lv_bar_set_range(obj, min, max);
+}
+
+/**
+ * Set the mode of slider.
+ * @param obj pointer to a slider object
+ * @param mode the mode of the slider. See ::lv_slider_mode_t
+ */
+static inline void lv_slider_set_mode(lv_obj_t * obj, lv_slider_mode_t mode)
+{
+ lv_bar_set_mode(obj, (lv_bar_mode_t)mode);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the value of the main knob of a slider
+ * @param obj pointer to a slider object
+ * @return the value of the main knob of the slider
+ */
+static inline int32_t lv_slider_get_value(const lv_obj_t * obj)
+{
+ return lv_bar_get_value(obj);
+}
+
+/**
+ * Get the value of the left knob of a slider
+ * @param obj pointer to a slider object
+ * @return the value of the left knob of the slider
+ */
+static inline int32_t lv_slider_get_left_value(const lv_obj_t * obj)
+{
+ return lv_bar_get_start_value(obj);
+}
+
+/**
+ * Get the minimum value of a slider
+ * @param obj pointer to a slider object
+ * @return the minimum value of the slider
+ */
+static inline int32_t lv_slider_get_min_value(const lv_obj_t * obj)
+{
+ return lv_bar_get_min_value(obj);
+}
+
+/**
+ * Get the maximum value of a slider
+ * @param obj pointer to a slider object
+ * @return the maximum value of the slider
+ */
+static inline int32_t lv_slider_get_max_value(const lv_obj_t * obj)
+{
+ return lv_bar_get_max_value(obj);
+}
+
+/**
+ * Give the slider is being dragged or not
+ * @param obj pointer to a slider object
+ * @return true: drag in progress false: not dragged
+ */
+bool lv_slider_is_dragged(const lv_obj_t * obj);
+
+/**
+ * Get the mode of the slider.
+ * @param obj pointer to a bar object
+ * @return see ::lv_slider_mode_t
+ */
+static inline lv_slider_mode_t lv_slider_get_mode(lv_obj_t * slider)
+{
+ lv_bar_mode_t mode = lv_bar_get_mode(slider);
+ if(mode == LV_BAR_MODE_SYMMETRICAL) return LV_SLIDER_MODE_SYMMETRICAL;
+ else if(mode == LV_BAR_MODE_RANGE) return LV_SLIDER_MODE_RANGE;
+ else return LV_SLIDER_MODE_NORMAL;
+}
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_SLIDER*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_SLIDER_H*/
diff --git a/lib/lvgl/src/widgets/lv_switch.c b/lib/lvgl/src/widgets/lv_switch.c
new file mode 100644
index 00000000..b3286104
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_switch.c
@@ -0,0 +1,277 @@
+/**
+ * @file lv_sw.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_switch.h"
+
+#if LV_USE_SWITCH != 0
+
+#include "../misc/lv_assert.h"
+#include "../misc/lv_math.h"
+#include "../misc/lv_anim.h"
+#include "../core/lv_indev.h"
+#include "../core/lv_disp.h"
+#include "lv_img.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_switch_class
+
+#define LV_SWITCH_IS_ANIMATING(sw) (((sw)->anim_state) != LV_SWITCH_ANIM_STATE_INV)
+
+/** Switch animation start value. (Not the real value of the switch just indicates process animation)*/
+#define LV_SWITCH_ANIM_STATE_START 0
+
+/** Switch animation end value. (Not the real value of the switch just indicates process animation)*/
+#define LV_SWITCH_ANIM_STATE_END 256
+
+/** Mark no animation is in progress*/
+#define LV_SWITCH_ANIM_STATE_INV -1
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_switch_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_switch_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_switch_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void draw_main(lv_event_t * e);
+
+static void lv_switch_anim_exec_cb(void * sw, int32_t value);
+static void lv_switch_trigger_anim(lv_obj_t * obj);
+static void lv_switch_anim_ready(lv_anim_t * a);
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_switch_class = {
+ .constructor_cb = lv_switch_constructor,
+ .destructor_cb = lv_switch_destructor,
+ .event_cb = lv_switch_event,
+ .width_def = (4 * LV_DPI_DEF) / 10,
+ .height_def = (4 * LV_DPI_DEF) / 17,
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
+ .instance_size = sizeof(lv_switch_t),
+ .base_class = &lv_obj_class
+};
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_switch_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;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_switch_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_switch_t * sw = (lv_switch_t *)obj;
+
+ sw->anim_state = LV_SWITCH_ANIM_STATE_INV;
+
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE);
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_switch_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ lv_switch_t * sw = (lv_switch_t *)obj;
+
+ lv_anim_del(sw, NULL);
+}
+
+static void lv_switch_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*/
+ 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_REFR_EXT_DRAW_SIZE) {
+ lv_coord_t knob_left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
+ lv_coord_t knob_right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
+ lv_coord_t knob_top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
+ lv_coord_t knob_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
+
+ /*The smaller size is the knob diameter*/
+ lv_coord_t knob_size = LV_MAX4(knob_left, knob_right, knob_bottom, knob_top);
+ knob_size += _LV_SWITCH_KNOB_EXT_AREA_CORRECTION;
+ knob_size += lv_obj_calculate_ext_draw_size(obj, LV_PART_KNOB);
+
+ lv_coord_t * s = lv_event_get_param(e);
+ *s = LV_MAX(*s, knob_size);
+ *s = LV_MAX(*s, lv_obj_calculate_ext_draw_size(obj, LV_PART_INDICATOR));
+ }
+ else if(code == LV_EVENT_VALUE_CHANGED) {
+ lv_switch_trigger_anim(obj);
+ lv_obj_invalidate(obj);
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ draw_main(e);
+ }
+}
+
+static void draw_main(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_switch_t * sw = (lv_switch_t *)obj;
+
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+
+ /*Calculate the indicator area*/
+ lv_coord_t bg_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t bg_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+ lv_coord_t bg_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t bg_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
+
+ /*Draw the indicator*/
+ /*Respect the background's padding*/
+ lv_area_t indic_area;
+ lv_area_copy(&indic_area, &obj->coords);
+ indic_area.x1 += bg_left;
+ indic_area.x2 -= bg_right;
+ indic_area.y1 += bg_top;
+ indic_area.y2 -= bg_bottom;
+
+ lv_draw_rect_dsc_t draw_indic_dsc;
+ lv_draw_rect_dsc_init(&draw_indic_dsc);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &draw_indic_dsc);
+ lv_draw_rect(draw_ctx, &draw_indic_dsc, &indic_area);
+
+ /*Draw the knob*/
+ lv_coord_t anim_value_x = 0;
+ lv_coord_t knob_size = lv_obj_get_height(obj);
+ lv_coord_t anim_length = lv_area_get_width(&obj->coords) - knob_size;
+
+ if(LV_SWITCH_IS_ANIMATING(sw)) {
+ /* Use the animation's coordinate */
+ anim_value_x = (anim_length * sw->anim_state) / LV_SWITCH_ANIM_STATE_END;
+ }
+ else {
+ /* Use LV_STATE_CHECKED to decide the coordinate */
+ bool chk = lv_obj_get_state(obj) & LV_STATE_CHECKED;
+ anim_value_x = chk ? anim_length : 0;
+ }
+
+ if(LV_BASE_DIR_RTL == lv_obj_get_style_base_dir(obj, LV_PART_MAIN)) {
+ anim_value_x = anim_length - anim_value_x;
+ }
+
+ lv_area_t knob_area;
+ knob_area.x1 = obj->coords.x1 + anim_value_x;
+ knob_area.x2 = knob_area.x1 + knob_size;
+
+ knob_area.y1 = obj->coords.y1;
+ knob_area.y2 = obj->coords.y2;
+
+ lv_coord_t knob_left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
+ lv_coord_t knob_right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
+ lv_coord_t knob_top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
+ lv_coord_t knob_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
+
+ /*Apply the paddings on the knob area*/
+ knob_area.x1 -= knob_left;
+ knob_area.x2 += knob_right;
+ knob_area.y1 -= knob_top;
+ knob_area.y2 += knob_bottom;
+
+ lv_draw_rect_dsc_t knob_rect_dsc;
+ lv_draw_rect_dsc_init(&knob_rect_dsc);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_KNOB, &knob_rect_dsc);
+
+ lv_draw_rect(draw_ctx, &knob_rect_dsc, &knob_area);
+}
+
+static void lv_switch_anim_exec_cb(void * var, int32_t value)
+{
+ lv_switch_t * sw = var;
+ sw->anim_state = value;
+ lv_obj_invalidate((lv_obj_t *)sw);
+}
+
+/**
+ * Resets the switch's animation state to "no animation in progress".
+ */
+static void lv_switch_anim_ready(lv_anim_t * a)
+{
+ lv_switch_t * sw = a->var;
+ sw->anim_state = LV_SWITCH_ANIM_STATE_INV;
+ lv_obj_invalidate((lv_obj_t *)sw);
+}
+
+/**
+ * Starts an animation for the switch knob. if the anim_time style property is greater than 0
+ * @param obj the switch to animate
+ */
+static void lv_switch_trigger_anim(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ lv_switch_t * sw = (lv_switch_t *)obj;
+
+ uint32_t anim_dur_full = lv_obj_get_style_anim_time(obj, LV_PART_MAIN);
+
+ if(anim_dur_full > 0) {
+ bool chk = lv_obj_get_state(obj) & LV_STATE_CHECKED;
+ int32_t anim_start;
+ int32_t anim_end;
+ /*No animation in progress -> simply set the values*/
+ if(sw->anim_state == LV_SWITCH_ANIM_STATE_INV) {
+ anim_start = chk ? LV_SWITCH_ANIM_STATE_START : LV_SWITCH_ANIM_STATE_END;
+ anim_end = chk ? LV_SWITCH_ANIM_STATE_END : LV_SWITCH_ANIM_STATE_START;
+ }
+ /*Animation in progress. Start from the animation end value*/
+ else {
+ anim_start = sw->anim_state;
+ anim_end = chk ? LV_SWITCH_ANIM_STATE_END : LV_SWITCH_ANIM_STATE_START;
+ }
+ /*Calculate actual animation duration*/
+ uint32_t anim_dur = (anim_dur_full * LV_ABS(anim_start - anim_end)) / LV_SWITCH_ANIM_STATE_END;
+
+ /*Stop the previous animation if it exists*/
+ lv_anim_del(sw, NULL);
+
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, sw);
+ lv_anim_set_exec_cb(&a, lv_switch_anim_exec_cb);
+ lv_anim_set_values(&a, anim_start, anim_end);
+ lv_anim_set_ready_cb(&a, lv_switch_anim_ready);
+ lv_anim_set_time(&a, anim_dur);
+ lv_anim_start(&a);
+ }
+}
+
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_switch.h b/lib/lvgl/src/widgets/lv_switch.h
new file mode 100644
index 00000000..83ca81bc
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_switch.h
@@ -0,0 +1,61 @@
+/**
+ * @file lv_switch.h
+ *
+ */
+
+#ifndef LV_SWITCH_H
+#define LV_SWITCH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_SWITCH != 0
+
+#include "../core/lv_obj.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/** Switch knob extra area correction factor */
+#define _LV_SWITCH_KNOB_EXT_AREA_CORRECTION 2
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+typedef struct {
+ lv_obj_t obj;
+ int32_t anim_state;
+} lv_switch_t;
+
+extern const lv_obj_class_t lv_switch_class;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a switch object
+ * @param parent pointer to an object, it will be the parent of the new switch
+ * @return pointer to the created switch
+ */
+lv_obj_t * lv_switch_create(lv_obj_t * parent);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_SWITCH*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_SWITCH_H*/
diff --git a/lib/lvgl/src/widgets/lv_table.c b/lib/lvgl/src/widgets/lv_table.c
new file mode 100644
index 00000000..5ff65ab2
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_table.c
@@ -0,0 +1,1007 @@
+/**
+ * @file lv_table.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_table.h"
+#if LV_USE_TABLE != 0
+
+#include "../core/lv_indev.h"
+#include "../misc/lv_assert.h"
+#include "../misc/lv_txt.h"
+#include "../misc/lv_txt_ap.h"
+#include "../misc/lv_math.h"
+#include "../misc/lv_printf.h"
+#include "../draw/lv_draw.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_table_class
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_table_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_table_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_table_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void draw_main(lv_event_t * e);
+static lv_coord_t get_row_height(lv_obj_t * obj, uint16_t row_id, const lv_font_t * font,
+ lv_coord_t letter_space, lv_coord_t line_space,
+ lv_coord_t cell_left, lv_coord_t cell_right, lv_coord_t cell_top, lv_coord_t cell_bottom);
+static void refr_size_form_row(lv_obj_t * obj, uint32_t start_row);
+static void refr_cell_size(lv_obj_t * obj, uint32_t row, uint32_t col);
+static lv_res_t get_pressed_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col);
+static size_t get_cell_txt_len(const char * txt);
+static void copy_cell_txt(char * dst, const char * txt);
+static void get_cell_area(lv_obj_t * obj, uint16_t row, uint16_t col, lv_area_t * area);
+
+static inline bool is_cell_empty(void * cell)
+{
+ return cell == NULL;
+}
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_table_class = {
+ .constructor_cb = lv_table_constructor,
+ .destructor_cb = lv_table_destructor,
+ .event_cb = lv_table_event,
+ .width_def = LV_SIZE_CONTENT,
+ .height_def = LV_SIZE_CONTENT,
+ .base_class = &lv_obj_class,
+ .editable = LV_OBJ_CLASS_EDITABLE_TRUE,
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
+ .instance_size = sizeof(lv_table_t),
+};
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_table_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
+ *====================*/
+
+void lv_table_set_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col, const char * txt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(txt);
+
+ lv_table_t * table = (lv_table_t *)obj;
+
+ /*Auto expand*/
+ if(col >= table->col_cnt) lv_table_set_col_cnt(obj, col + 1);
+ if(row >= table->row_cnt) lv_table_set_row_cnt(obj, row + 1);
+
+ uint32_t cell = row * table->col_cnt + col;
+ lv_table_cell_ctrl_t ctrl = 0;
+
+ /*Save the control byte*/
+ if(table->cell_data[cell]) ctrl = table->cell_data[cell][0];
+
+ size_t to_allocate = get_cell_txt_len(txt);
+
+ table->cell_data[cell] = lv_mem_realloc(table->cell_data[cell], to_allocate);
+ LV_ASSERT_MALLOC(table->cell_data[cell]);
+ if(table->cell_data[cell] == NULL) return;
+
+ copy_cell_txt(table->cell_data[cell], txt);
+
+ table->cell_data[cell][0] = ctrl;
+ refr_cell_size(obj, row, col);
+}
+
+void lv_table_set_cell_value_fmt(lv_obj_t * obj, uint16_t row, uint16_t col, const char * fmt, ...)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(fmt);
+
+ lv_table_t * table = (lv_table_t *)obj;
+ if(col >= table->col_cnt) {
+ lv_table_set_col_cnt(obj, col + 1);
+ }
+
+ /*Auto expand*/
+ if(row >= table->row_cnt) {
+ lv_table_set_row_cnt(obj, row + 1);
+ }
+
+ uint32_t cell = row * table->col_cnt + col;
+ lv_table_cell_ctrl_t ctrl = 0;
+
+ /*Save the control byte*/
+ if(table->cell_data[cell]) ctrl = table->cell_data[cell][0];
+
+ va_list ap, ap2;
+ va_start(ap, fmt);
+ va_copy(ap2, ap);
+
+ /*Allocate space for the new text by using trick from C99 standard section 7.19.6.12*/
+ uint32_t len = lv_vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ /*Put together the text according to the format string*/
+ char * raw_txt = lv_mem_buf_get(len + 1);
+ LV_ASSERT_MALLOC(raw_txt);
+ if(raw_txt == NULL) {
+ va_end(ap2);
+ return;
+ }
+
+ lv_vsnprintf(raw_txt, len + 1, fmt, ap2);
+
+ /*Get the size of the Arabic text and process it*/
+ size_t len_ap = _lv_txt_ap_calc_bytes_cnt(raw_txt);
+ table->cell_data[cell] = lv_mem_realloc(table->cell_data[cell], len_ap + 1);
+ LV_ASSERT_MALLOC(table->cell_data[cell]);
+ if(table->cell_data[cell] == NULL) {
+ va_end(ap2);
+ return;
+ }
+ _lv_txt_ap_proc(raw_txt, &table->cell_data[cell][1]);
+
+ lv_mem_buf_release(raw_txt);
+#else
+ table->cell_data[cell] = lv_mem_realloc(table->cell_data[cell], len + 2); /*+1: trailing '\0; +1: format byte*/
+ LV_ASSERT_MALLOC(table->cell_data[cell]);
+ if(table->cell_data[cell] == NULL) {
+ va_end(ap2);
+ return;
+ }
+
+ table->cell_data[cell][len + 1] = 0; /*Ensure NULL termination*/
+
+ lv_vsnprintf(&table->cell_data[cell][1], len + 1, fmt, ap2);
+#endif
+
+ va_end(ap2);
+
+ table->cell_data[cell][0] = ctrl;
+
+ refr_cell_size(obj, row, col);
+}
+
+void lv_table_set_row_cnt(lv_obj_t * obj, uint16_t row_cnt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+
+ if(table->row_cnt == row_cnt) return;
+
+ uint16_t old_row_cnt = table->row_cnt;
+ table->row_cnt = row_cnt;
+
+ table->row_h = lv_mem_realloc(table->row_h, table->row_cnt * sizeof(table->row_h[0]));
+ LV_ASSERT_MALLOC(table->row_h);
+ if(table->row_h == NULL) return;
+
+ /*Free the unused cells*/
+ if(old_row_cnt > row_cnt) {
+ uint16_t old_cell_cnt = old_row_cnt * table->col_cnt;
+ uint32_t new_cell_cnt = table->col_cnt * table->row_cnt;
+ uint32_t i;
+ for(i = new_cell_cnt; i < old_cell_cnt; i++) {
+ lv_mem_free(table->cell_data[i]);
+ }
+ }
+
+ table->cell_data = lv_mem_realloc(table->cell_data, table->row_cnt * table->col_cnt * sizeof(char *));
+ LV_ASSERT_MALLOC(table->cell_data);
+ if(table->cell_data == NULL) return;
+
+ /*Initialize the new fields*/
+ if(old_row_cnt < row_cnt) {
+ uint32_t old_cell_cnt = old_row_cnt * table->col_cnt;
+ uint32_t new_cell_cnt = table->col_cnt * table->row_cnt;
+ lv_memset_00(&table->cell_data[old_cell_cnt], (new_cell_cnt - old_cell_cnt) * sizeof(table->cell_data[0]));
+ }
+
+ refr_size_form_row(obj, 0);
+}
+
+void lv_table_set_col_cnt(lv_obj_t * obj, uint16_t col_cnt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+
+ if(table->col_cnt == col_cnt) return;
+
+ uint16_t old_col_cnt = table->col_cnt;
+ table->col_cnt = col_cnt;
+
+ char ** new_cell_data = lv_mem_alloc(table->row_cnt * table->col_cnt * sizeof(char *));
+ LV_ASSERT_MALLOC(new_cell_data);
+ if(new_cell_data == NULL) return;
+ uint32_t new_cell_cnt = table->col_cnt * table->row_cnt;
+
+ lv_memset_00(new_cell_data, new_cell_cnt * sizeof(table->cell_data[0]));
+
+ /*The new column(s) messes up the mapping of `cell_data`*/
+ uint32_t old_col_start;
+ uint32_t new_col_start;
+ uint32_t min_col_cnt = LV_MIN(old_col_cnt, col_cnt);
+ uint32_t row;
+ for(row = 0; row < table->row_cnt; row++) {
+ old_col_start = row * old_col_cnt;
+ new_col_start = row * col_cnt;
+
+ lv_memcpy_small(&new_cell_data[new_col_start], &table->cell_data[old_col_start],
+ sizeof(new_cell_data[0]) * min_col_cnt);
+
+ /*Free the old cells (only if the table becomes smaller)*/
+ int32_t i;
+ for(i = 0; i < (int32_t)old_col_cnt - col_cnt; i++) {
+ uint32_t idx = old_col_start + min_col_cnt + i;
+ lv_mem_free(table->cell_data[idx]);
+ table->cell_data[idx] = NULL;
+ }
+ }
+
+ lv_mem_free(table->cell_data);
+ table->cell_data = new_cell_data;
+
+ /*Initialize the new column widths if any*/
+ table->col_w = lv_mem_realloc(table->col_w, col_cnt * sizeof(table->col_w[0]));
+ LV_ASSERT_MALLOC(table->col_w);
+ if(table->col_w == NULL) return;
+
+ uint32_t col;
+ for(col = old_col_cnt; col < col_cnt; col++) {
+ table->col_w[col] = LV_DPI_DEF;
+ }
+
+
+ refr_size_form_row(obj, 0) ;
+}
+
+void lv_table_set_col_width(lv_obj_t * obj, uint16_t col_id, lv_coord_t w)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+
+ /*Auto expand*/
+ if(col_id >= table->col_cnt) lv_table_set_col_cnt(obj, col_id + 1);
+
+ table->col_w[col_id] = w;
+ refr_size_form_row(obj, 0);
+}
+
+void lv_table_add_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+
+ /*Auto expand*/
+ if(col >= table->col_cnt) lv_table_set_col_cnt(obj, col + 1);
+ if(row >= table->row_cnt) lv_table_set_row_cnt(obj, row + 1);
+
+ uint32_t cell = row * table->col_cnt + col;
+
+ if(is_cell_empty(table->cell_data[cell])) {
+ table->cell_data[cell] = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/
+ LV_ASSERT_MALLOC(table->cell_data[cell]);
+ if(table->cell_data[cell] == NULL) return;
+
+ table->cell_data[cell][0] = 0;
+ table->cell_data[cell][1] = '\0';
+ }
+
+ table->cell_data[cell][0] |= ctrl;
+}
+
+void lv_table_clear_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+
+ /*Auto expand*/
+ if(col >= table->col_cnt) lv_table_set_col_cnt(obj, col + 1);
+ if(row >= table->row_cnt) lv_table_set_row_cnt(obj, row + 1);
+
+ uint32_t cell = row * table->col_cnt + col;
+
+ if(is_cell_empty(table->cell_data[cell])) {
+ table->cell_data[cell] = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/
+ LV_ASSERT_MALLOC(table->cell_data[cell]);
+ if(table->cell_data[cell] == NULL) return;
+
+ table->cell_data[cell][0] = 0;
+ table->cell_data[cell][1] = '\0';
+ }
+
+ table->cell_data[cell][0] &= (~ctrl);
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+const char * lv_table_get_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+ if(row >= table->row_cnt || col >= table->col_cnt) {
+ LV_LOG_WARN("invalid row or column");
+ return "";
+ }
+ uint32_t cell = row * table->col_cnt + col;
+
+ if(is_cell_empty(table->cell_data[cell])) return "";
+
+ return &table->cell_data[cell][1]; /*Skip the format byte*/
+}
+
+uint16_t lv_table_get_row_cnt(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+ return table->row_cnt;
+}
+
+uint16_t lv_table_get_col_cnt(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+ return table->col_cnt;
+}
+
+lv_coord_t lv_table_get_col_width(lv_obj_t * obj, uint16_t col)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+
+ if(col >= table->col_cnt) {
+ LV_LOG_WARN("lv_table_set_col_width: too big 'col_id'. Must be < LV_TABLE_COL_MAX.");
+ return 0;
+ }
+
+ return table->col_w[col];
+}
+
+bool lv_table_has_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+ if(row >= table->row_cnt || col >= table->col_cnt) {
+ LV_LOG_WARN("lv_table_get_cell_crop: invalid row or column");
+ return false;
+ }
+ uint32_t cell = row * table->col_cnt + col;
+
+ if(is_cell_empty(table->cell_data[cell])) return false;
+ else return (table->cell_data[cell][0] & ctrl) == ctrl;
+}
+
+void lv_table_get_selected_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col)
+{
+ lv_table_t * table = (lv_table_t *)obj;
+ *row = table->row_act;
+ *col = table->col_act;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_table_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_table_t * table = (lv_table_t *)obj;
+
+ table->col_cnt = 1;
+ table->row_cnt = 1;
+ table->col_w = lv_mem_alloc(table->col_cnt * sizeof(table->col_w[0]));
+ table->row_h = lv_mem_alloc(table->row_cnt * sizeof(table->row_h[0]));
+ table->col_w[0] = LV_DPI_DEF;
+ table->row_h[0] = LV_DPI_DEF;
+ table->cell_data = lv_mem_realloc(table->cell_data, table->row_cnt * table->col_cnt * sizeof(char *));
+ table->cell_data[0] = NULL;
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_table_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ lv_table_t * table = (lv_table_t *)obj;
+ /*Free the cell texts*/
+ uint16_t i;
+ for(i = 0; i < table->col_cnt * table->row_cnt; i++) {
+ if(table->cell_data[i]) {
+ lv_mem_free(table->cell_data[i]);
+ table->cell_data[i] = NULL;
+ }
+ }
+
+ if(table->cell_data) lv_mem_free(table->cell_data);
+ if(table->row_h) lv_mem_free(table->row_h);
+ if(table->col_w) lv_mem_free(table->col_w);
+}
+
+static void lv_table_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*/
+ 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_table_t * table = (lv_table_t *)obj;
+
+ if(code == LV_EVENT_STYLE_CHANGED) {
+ refr_size_form_row(obj, 0);
+ }
+ else if(code == LV_EVENT_GET_SELF_SIZE) {
+ lv_point_t * p = lv_event_get_param(e);
+ uint32_t i;
+ lv_coord_t w = 0;
+ for(i = 0; i < table->col_cnt; i++) w += table->col_w[i];
+
+ lv_coord_t h = 0;
+ for(i = 0; i < table->row_cnt; i++) h += table->row_h[i];
+
+ p->x = w - 1;
+ p->y = h - 1;
+ }
+ else if(code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING) {
+ uint16_t col;
+ uint16_t row;
+ lv_res_t pr_res = get_pressed_cell(obj, &row, &col);
+
+ if(pr_res == LV_RES_OK && (table->col_act != col || table->row_act != row)) {
+ table->col_act = col;
+ table->row_act = row;
+ lv_obj_invalidate(obj);
+ }
+ }
+ else if(code == LV_EVENT_RELEASED) {
+ lv_obj_invalidate(obj);
+ lv_indev_t * indev = lv_indev_get_act();
+ lv_obj_t * scroll_obj = lv_indev_get_scroll_obj(indev);
+ if(table->col_act != LV_TABLE_CELL_NONE && table->row_act != LV_TABLE_CELL_NONE && scroll_obj == NULL) {
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
+ if(res != LV_RES_OK) return;
+ }
+
+ lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
+ if(indev_type == LV_INDEV_TYPE_POINTER || indev_type == LV_INDEV_TYPE_BUTTON) {
+ table->col_act = LV_TABLE_CELL_NONE;
+ table->row_act = LV_TABLE_CELL_NONE;
+ }
+ }
+ else if(code == LV_EVENT_FOCUSED) {
+ lv_obj_invalidate(obj);
+ }
+ else if(code == LV_EVENT_KEY) {
+ int32_t c = *((int32_t *)lv_event_get_param(e));
+ int32_t col = table->col_act;
+ int32_t row = table->row_act;
+ if(col == LV_TABLE_CELL_NONE || row == LV_TABLE_CELL_NONE) {
+ table->col_act = 0;
+ table->row_act = 0;
+ lv_obj_invalidate(obj);
+ return;
+ }
+
+ if(col >= table->col_cnt) col = 0;
+ if(row >= table->row_cnt) row = 0;
+
+ if(c == LV_KEY_LEFT) col--;
+ else if(c == LV_KEY_RIGHT) col++;
+ else if(c == LV_KEY_UP) row--;
+ else if(c == LV_KEY_DOWN) row++;
+ else return;
+
+ if(col >= table->col_cnt) {
+ if(row < table->row_cnt - 1) {
+ col = 0;
+ row++;
+ }
+ else {
+ col = table->col_cnt - 1;
+ }
+ }
+ else if(col < 0) {
+ if(row != 0) {
+ col = table->col_cnt - 1;
+ row--;
+ }
+ else {
+ col = 0;
+ }
+ }
+
+ if(row >= table->row_cnt) {
+ row = table->row_cnt - 1;
+ }
+ else if(row < 0) {
+ row = 0;
+ }
+
+ if(table->col_act != col || table->row_act != row) {
+ table->col_act = col;
+ table->row_act = row;
+ lv_obj_invalidate(obj);
+
+ res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
+ if(res != LV_RES_OK) return;
+ }
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ draw_main(e);
+ }
+}
+
+
+static void draw_main(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_table_t * table = (lv_table_t *)obj;
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+ lv_area_t clip_area;
+ if(!_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area)) return;
+
+ const lv_area_t * clip_area_ori = draw_ctx->clip_area;
+ draw_ctx->clip_area = &clip_area;
+
+ lv_point_t txt_size;
+ lv_area_t cell_area;
+
+ lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
+ lv_coord_t bg_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t bg_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
+ lv_coord_t bg_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t bg_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
+
+ lv_state_t state_ori = obj->state;
+ obj->state = LV_STATE_DEFAULT;
+ obj->skip_trans = 1;
+ lv_draw_rect_dsc_t rect_dsc_def;
+ lv_draw_rect_dsc_t rect_dsc_act; /*Passed to the event to modify it*/
+ lv_draw_rect_dsc_init(&rect_dsc_def);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &rect_dsc_def);
+
+ lv_draw_label_dsc_t label_dsc_def;
+ lv_draw_label_dsc_t label_dsc_act; /*Passed to the event to modify it*/
+ lv_draw_label_dsc_init(&label_dsc_def);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &label_dsc_def);
+ obj->state = state_ori;
+ obj->skip_trans = 0;
+
+ uint16_t col;
+ uint16_t row;
+ uint16_t cell = 0;
+
+ cell_area.y2 = obj->coords.y1 + bg_top - 1 - lv_obj_get_scroll_y(obj) + border_width;
+ lv_coord_t scroll_x = lv_obj_get_scroll_x(obj) ;
+ bool rtl = lv_obj_get_style_base_dir(obj, LV_PART_MAIN) == LV_BASE_DIR_RTL;
+
+ /*Handle custom drawer*/
+ 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_TABLE_DRAW_PART_CELL;
+ part_draw_dsc.rect_dsc = &rect_dsc_act;
+ part_draw_dsc.label_dsc = &label_dsc_act;
+
+ for(row = 0; row < table->row_cnt; row++) {
+ lv_coord_t h_row = table->row_h[row];
+
+ cell_area.y1 = cell_area.y2 + 1;
+ cell_area.y2 = cell_area.y1 + h_row - 1;
+
+ if(cell_area.y1 > clip_area.y2) break;
+
+ if(rtl) cell_area.x1 = obj->coords.x2 - bg_right - 1 - scroll_x - border_width;
+ else cell_area.x2 = obj->coords.x1 + bg_left - 1 - scroll_x + border_width;
+
+ for(col = 0; col < table->col_cnt; col++) {
+ lv_table_cell_ctrl_t ctrl = 0;
+ if(table->cell_data[cell]) ctrl = table->cell_data[cell][0];
+
+ if(rtl) {
+ cell_area.x2 = cell_area.x1 - 1;
+ cell_area.x1 = cell_area.x2 - table->col_w[col] + 1;
+ }
+ else {
+ cell_area.x1 = cell_area.x2 + 1;
+ cell_area.x2 = cell_area.x1 + table->col_w[col] - 1;
+ }
+
+ uint16_t col_merge = 0;
+ for(col_merge = 0; col_merge + col < table->col_cnt - 1; col_merge++) {
+ char * next_cell_data = table->cell_data[cell + col_merge];
+
+ if(is_cell_empty(next_cell_data)) break;
+
+ lv_table_cell_ctrl_t merge_ctrl = (lv_table_cell_ctrl_t) next_cell_data[0];
+ if(merge_ctrl & LV_TABLE_CELL_CTRL_MERGE_RIGHT) {
+ lv_coord_t offset = table->col_w[col + col_merge + 1];
+
+ if(rtl) cell_area.x1 -= offset;
+ else cell_area.x2 += offset;
+ }
+ else {
+ break;
+ }
+ }
+
+ if(cell_area.y2 < clip_area.y1) {
+ cell += col_merge + 1;
+ col += col_merge;
+ continue;
+ }
+
+ /*Expand the cell area with a half border to avoid drawing 2 borders next to each other*/
+ lv_area_t cell_area_border;
+ lv_area_copy(&cell_area_border, &cell_area);
+ if((rect_dsc_def.border_side & LV_BORDER_SIDE_LEFT) && cell_area_border.x1 > obj->coords.x1 + bg_left) {
+ cell_area_border.x1 -= rect_dsc_def.border_width / 2;
+ }
+ if((rect_dsc_def.border_side & LV_BORDER_SIDE_TOP) && cell_area_border.y1 > obj->coords.y1 + bg_top) {
+ cell_area_border.y1 -= rect_dsc_def.border_width / 2;
+ }
+ if((rect_dsc_def.border_side & LV_BORDER_SIDE_RIGHT) && cell_area_border.x2 < obj->coords.x2 - bg_right - 1) {
+ cell_area_border.x2 += rect_dsc_def.border_width / 2 + (rect_dsc_def.border_width & 0x1);
+ }
+ if((rect_dsc_def.border_side & LV_BORDER_SIDE_BOTTOM) &&
+ cell_area_border.y2 < obj->coords.y2 - bg_bottom - 1) {
+ cell_area_border.y2 += rect_dsc_def.border_width / 2 + (rect_dsc_def.border_width & 0x1);
+ }
+
+ lv_state_t cell_state = LV_STATE_DEFAULT;
+ if(row == table->row_act && col == table->col_act) {
+ if(!(obj->state & LV_STATE_SCROLLED) && (obj->state & LV_STATE_PRESSED)) cell_state |= LV_STATE_PRESSED;
+ if(obj->state & LV_STATE_FOCUSED) cell_state |= LV_STATE_FOCUSED;
+ if(obj->state & LV_STATE_FOCUS_KEY) cell_state |= LV_STATE_FOCUS_KEY;
+ if(obj->state & LV_STATE_EDITED) cell_state |= LV_STATE_EDITED;
+ }
+
+ /*Set up the draw descriptors*/
+ if(cell_state == LV_STATE_DEFAULT) {
+ lv_memcpy(&rect_dsc_act, &rect_dsc_def, sizeof(lv_draw_rect_dsc_t));
+ lv_memcpy(&label_dsc_act, &label_dsc_def, sizeof(lv_draw_label_dsc_t));
+ }
+ /*In other cases get the styles directly without caching them*/
+ else {
+ obj->state = cell_state;
+ obj->skip_trans = 1;
+ lv_draw_rect_dsc_init(&rect_dsc_act);
+ lv_draw_label_dsc_init(&label_dsc_act);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &rect_dsc_act);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_ITEMS, &label_dsc_act);
+ obj->state = state_ori;
+ obj->skip_trans = 0;
+ }
+
+ part_draw_dsc.draw_area = &cell_area_border;
+ part_draw_dsc.id = row * table->col_cnt + col;
+ lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
+
+ lv_draw_rect(draw_ctx, &rect_dsc_act, &cell_area_border);
+
+ if(table->cell_data[cell]) {
+ const lv_coord_t cell_left = lv_obj_get_style_pad_left(obj, LV_PART_ITEMS);
+ const lv_coord_t cell_right = lv_obj_get_style_pad_right(obj, LV_PART_ITEMS);
+ const lv_coord_t cell_top = lv_obj_get_style_pad_top(obj, LV_PART_ITEMS);
+ const lv_coord_t cell_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_ITEMS);
+ lv_text_flag_t txt_flags = LV_TEXT_FLAG_NONE;
+ lv_area_t txt_area;
+
+ txt_area.x1 = cell_area.x1 + cell_left;
+ txt_area.x2 = cell_area.x2 - cell_right;
+ txt_area.y1 = cell_area.y1 + cell_top;
+ txt_area.y2 = cell_area.y2 - cell_bottom;
+
+ /*Align the content to the middle if not cropped*/
+ bool crop = ctrl & LV_TABLE_CELL_CTRL_TEXT_CROP ? true : false;
+ if(crop) txt_flags = LV_TEXT_FLAG_EXPAND;
+
+ lv_txt_get_size(&txt_size, table->cell_data[cell] + 1, label_dsc_def.font,
+ label_dsc_act.letter_space, label_dsc_act.line_space,
+ lv_area_get_width(&txt_area), txt_flags);
+
+ /*Align the content to the middle if not cropped*/
+ if(!crop) {
+ txt_area.y1 = cell_area.y1 + h_row / 2 - txt_size.y / 2;
+ txt_area.y2 = cell_area.y1 + h_row / 2 + txt_size.y / 2;
+ }
+
+ lv_area_t label_clip_area;
+ bool label_mask_ok;
+ label_mask_ok = _lv_area_intersect(&label_clip_area, &clip_area, &cell_area);
+ if(label_mask_ok) {
+ draw_ctx->clip_area = &label_clip_area;
+ lv_draw_label(draw_ctx, &label_dsc_act, &txt_area, table->cell_data[cell] + 1, NULL);
+ draw_ctx->clip_area = &clip_area;
+ }
+ }
+
+ lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
+
+ cell += col_merge + 1;
+ col += col_merge;
+ }
+ }
+
+ draw_ctx->clip_area = clip_area_ori;
+}
+
+/* Refreshes size of the table starting from @start_row row */
+static void refr_size_form_row(lv_obj_t * obj, uint32_t start_row)
+{
+ const lv_coord_t cell_pad_left = lv_obj_get_style_pad_left(obj, LV_PART_ITEMS);
+ const lv_coord_t cell_pad_right = lv_obj_get_style_pad_right(obj, LV_PART_ITEMS);
+ const lv_coord_t cell_pad_top = lv_obj_get_style_pad_top(obj, LV_PART_ITEMS);
+ const lv_coord_t cell_pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_ITEMS);
+
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_ITEMS);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_ITEMS);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_ITEMS);
+
+ const lv_coord_t minh = lv_obj_get_style_min_height(obj, LV_PART_ITEMS);
+ const lv_coord_t maxh = lv_obj_get_style_max_height(obj, LV_PART_ITEMS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+ uint32_t i;
+ for(i = start_row; i < table->row_cnt; i++) {
+ lv_coord_t calculated_height = get_row_height(obj, i, font, letter_space, line_space,
+ cell_pad_left, cell_pad_right, cell_pad_top, cell_pad_bottom);
+ table->row_h[i] = LV_CLAMP(minh, calculated_height, maxh);
+ }
+
+ lv_obj_refresh_self_size(obj);
+ lv_obj_invalidate(obj);
+}
+
+
+static void refr_cell_size(lv_obj_t * obj, uint32_t row, uint32_t col)
+{
+ const lv_coord_t cell_pad_left = lv_obj_get_style_pad_left(obj, LV_PART_ITEMS);
+ const lv_coord_t cell_pad_right = lv_obj_get_style_pad_right(obj, LV_PART_ITEMS);
+ const lv_coord_t cell_pad_top = lv_obj_get_style_pad_top(obj, LV_PART_ITEMS);
+ const lv_coord_t cell_pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_ITEMS);
+
+ lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_ITEMS);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_ITEMS);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_ITEMS);
+
+ const lv_coord_t minh = lv_obj_get_style_min_height(obj, LV_PART_ITEMS);
+ const lv_coord_t maxh = lv_obj_get_style_max_height(obj, LV_PART_ITEMS);
+
+ lv_table_t * table = (lv_table_t *)obj;
+ lv_coord_t calculated_height = get_row_height(obj, row, font, letter_space, line_space,
+ cell_pad_left, cell_pad_right, cell_pad_top, cell_pad_bottom);
+
+ lv_coord_t prev_row_size = table->row_h[row];
+ table->row_h[row] = LV_CLAMP(minh, calculated_height, maxh);
+
+ /*If the row height havn't changed invalidate only this cell*/
+ if(prev_row_size == table->row_h[row]) {
+ lv_area_t cell_area;
+ get_cell_area(obj, row, col, &cell_area);
+ lv_area_move(&cell_area, obj->coords.x1, obj->coords.y1);
+ lv_obj_invalidate_area(obj, &cell_area);
+ }
+ else {
+ lv_obj_refresh_self_size(obj);
+ lv_obj_invalidate(obj);
+ }
+}
+
+static lv_coord_t get_row_height(lv_obj_t * obj, uint16_t row_id, const lv_font_t * font,
+ lv_coord_t letter_space, lv_coord_t line_space,
+ lv_coord_t cell_left, lv_coord_t cell_right, lv_coord_t cell_top, lv_coord_t cell_bottom)
+{
+ lv_table_t * table = (lv_table_t *)obj;
+
+ lv_coord_t h_max = lv_font_get_line_height(font) + cell_top + cell_bottom;
+ /* Calculate the cell_data index where to start */
+ uint16_t row_start = row_id * table->col_cnt;
+
+ /* Traverse the cells in the row_id row */
+ uint16_t cell;
+ uint16_t col;
+ for(cell = row_start, col = 0; cell < row_start + table->col_cnt; cell++, col++) {
+ char * cell_data = table->cell_data[cell];
+
+ if(is_cell_empty(cell_data)) {
+ continue;
+ }
+
+ lv_coord_t txt_w = table->col_w[col];
+
+ /* Traverse the current row from the first until the penultimate column.
+ * Increment the text width if the cell has the LV_TABLE_CELL_CTRL_MERGE_RIGHT control,
+ * exit the traversal when the current cell control is not LV_TABLE_CELL_CTRL_MERGE_RIGHT */
+ uint16_t col_merge = 0;
+ for(col_merge = 0; col_merge + col < table->col_cnt - 1; col_merge++) {
+ char * next_cell_data = table->cell_data[cell + col_merge];
+
+ if(is_cell_empty(next_cell_data)) break;
+
+ lv_table_cell_ctrl_t ctrl = (lv_table_cell_ctrl_t) next_cell_data[0];
+ if(ctrl & LV_TABLE_CELL_CTRL_MERGE_RIGHT) {
+ txt_w += table->col_w[col + col_merge + 1];
+ }
+ else {
+ break;
+ }
+ }
+
+ lv_table_cell_ctrl_t ctrl = (lv_table_cell_ctrl_t) cell_data[0];
+
+ /*When cropping the text we can assume the row height is equal to the line height*/
+ if(ctrl & LV_TABLE_CELL_CTRL_TEXT_CROP) {
+ h_max = LV_MAX(lv_font_get_line_height(font) + cell_top + cell_bottom,
+ h_max);
+ }
+ /*Else we have to calculate the height of the cell text*/
+ else {
+ lv_point_t txt_size;
+ txt_w -= cell_left + cell_right;
+
+ lv_txt_get_size(&txt_size, table->cell_data[cell] + 1, font,
+ letter_space, line_space, txt_w, LV_TEXT_FLAG_NONE);
+
+ h_max = LV_MAX(txt_size.y + cell_top + cell_bottom, h_max);
+ /*Skip until one element after the last merged column*/
+ cell += col_merge;
+ col += col_merge;
+ }
+ }
+
+ return h_max;
+}
+
+static lv_res_t get_pressed_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col)
+{
+ lv_table_t * table = (lv_table_t *)obj;
+
+ lv_indev_type_t type = lv_indev_get_type(lv_indev_get_act());
+ if(type != LV_INDEV_TYPE_POINTER && type != LV_INDEV_TYPE_BUTTON) {
+ if(col) *col = LV_TABLE_CELL_NONE;
+ if(row) *row = LV_TABLE_CELL_NONE;
+ return LV_RES_INV;
+ }
+
+ lv_point_t p;
+ lv_indev_get_point(lv_indev_get_act(), &p);
+
+ lv_coord_t tmp;
+ if(col) {
+ lv_coord_t x = p.x + lv_obj_get_scroll_x(obj);
+
+ if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) == LV_BASE_DIR_RTL) {
+ x = obj->coords.x2 - lv_obj_get_style_pad_right(obj, LV_PART_MAIN) - x;
+ }
+ else {
+ x -= obj->coords.x1;
+ x -= lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ }
+
+ *col = 0;
+ tmp = 0;
+ for(*col = 0; *col < table->col_cnt; (*col)++) {
+ tmp += table->col_w[*col];
+ if(x < tmp) break;
+ }
+ }
+
+ if(row) {
+ lv_coord_t y = p.y + lv_obj_get_scroll_y(obj);;
+ y -= obj->coords.y1;
+ y -= lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+
+ *row = 0;
+ tmp = 0;
+
+ for(*row = 0; *row < table->row_cnt; (*row)++) {
+ tmp += table->row_h[*row];
+ if(y < tmp) break;
+ }
+ }
+
+ return LV_RES_OK;
+}
+
+/* Returns number of bytes to allocate based on chars configuration */
+static size_t get_cell_txt_len(const char * txt)
+{
+ size_t retval = 0;
+
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ retval = _lv_txt_ap_calc_bytes_cnt(txt) + 1;
+#else
+ /* cell_data layout: [ctrl][txt][trailing '\0' terminator]
+ * +2 because of the trailing '\0' and the ctrl */
+ retval = strlen(txt) + 2;
+#endif
+
+ return retval;
+}
+
+/* Copy txt into dst skipping the format byte */
+static void copy_cell_txt(char * dst, const char * txt)
+{
+#if LV_USE_ARABIC_PERSIAN_CHARS
+ _lv_txt_ap_proc(txt, &dst[1]);
+#else
+ strcpy(&dst[1], txt);
+#endif
+}
+
+static void get_cell_area(lv_obj_t * obj, uint16_t row, uint16_t col, lv_area_t * area)
+{
+ lv_table_t * table = (lv_table_t *)obj;
+
+ uint32_t c;
+ area->x1 = 0;
+ for(c = 0; c < col; c++) {
+ area->x1 += table->col_w[c];
+ }
+
+ bool rtl = lv_obj_get_style_base_dir(obj, LV_PART_MAIN) == LV_BASE_DIR_RTL;
+ if(rtl) {
+ area->x1 += lv_obj_get_scroll_x(obj);
+ lv_coord_t w = lv_obj_get_width(obj);
+ area->x2 = w - area->x1 - lv_obj_get_style_pad_right(obj, 0);
+ area->x1 = area->x2 - table->col_w[col];
+ }
+ else {
+ area->x1 -= lv_obj_get_scroll_x(obj);
+ area->x1 += lv_obj_get_style_pad_left(obj, 0);
+ area->x2 = area->x1 + table->col_w[col] - 1;
+ }
+
+ uint32_t r;
+ area->y1 = 0;
+ for(r = 0; r < row; r++) {
+ area->y1 += table->row_h[r];
+ }
+
+ area->y1 += lv_obj_get_style_pad_top(obj, 0);
+ area->y1 -= lv_obj_get_scroll_y(obj);
+ area->y2 = area->y1 + table->row_h[row] - 1;
+
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_table.h b/lib/lvgl/src/widgets/lv_table.h
new file mode 100644
index 00000000..91062708
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_table.h
@@ -0,0 +1,210 @@
+/**
+ * @file lv_table.h
+ *
+ */
+
+#ifndef LV_TABLE_H
+#define LV_TABLE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_TABLE != 0
+
+/*Testing of dependencies*/
+#if LV_USE_LABEL == 0
+#error "lv_table: lv_label is required. Enable it in lv_conf.h (LV_USE_LABEL 1)"
+#endif
+
+#include "../core/lv_obj.h"
+#include "lv_label.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define LV_TABLE_CELL_NONE 0XFFFF
+LV_EXPORT_CONST_INT(LV_TABLE_CELL_NONE);
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+enum {
+ LV_TABLE_CELL_CTRL_MERGE_RIGHT = 1 << 0,
+ LV_TABLE_CELL_CTRL_TEXT_CROP = 1 << 1,
+ LV_TABLE_CELL_CTRL_CUSTOM_1 = 1 << 4,
+ LV_TABLE_CELL_CTRL_CUSTOM_2 = 1 << 5,
+ LV_TABLE_CELL_CTRL_CUSTOM_3 = 1 << 6,
+ LV_TABLE_CELL_CTRL_CUSTOM_4 = 1 << 7,
+};
+
+typedef uint8_t lv_table_cell_ctrl_t;
+
+/*Data of table*/
+typedef struct {
+ lv_obj_t obj;
+ uint16_t col_cnt;
+ uint16_t row_cnt;
+ char ** cell_data;
+ lv_coord_t * row_h;
+ lv_coord_t * col_w;
+ uint16_t col_act;
+ uint16_t row_act;
+} lv_table_t;
+
+extern const lv_obj_class_t lv_table_class;
+
+/**
+ * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_table_class`
+ * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END`
+ */
+typedef enum {
+ LV_TABLE_DRAW_PART_CELL, /**< A cell*/
+} lv_table_draw_part_type_t;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a table object
+ * @param parent pointer to an object, it will be the parent of the new table
+ * @return pointer to the created table
+ */
+lv_obj_t * lv_table_create(lv_obj_t * parent);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set the value of a cell.
+ * @param obj pointer to a Table object
+ * @param row id of the row [0 .. row_cnt -1]
+ * @param col id of the column [0 .. col_cnt -1]
+ * @param txt text to display in the cell. It will be copied and saved so this variable is not required after this function call.
+ * @note New roes/columns are added automatically if required
+ */
+void lv_table_set_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col, const char * txt);
+
+/**
+ * Set the value of a cell. Memory will be allocated to store the text by the table.
+ * @param obj pointer to a Table object
+ * @param row id of the row [0 .. row_cnt -1]
+ * @param col id of the column [0 .. col_cnt -1]
+ * @param fmt `printf`-like format
+ * @note New roes/columns are added automatically if required
+ */
+void lv_table_set_cell_value_fmt(lv_obj_t * obj, uint16_t row, uint16_t col, const char * fmt, ...);
+
+/**
+ * Set the number of rows
+ * @param obj table pointer to a Table object
+ * @param row_cnt number of rows
+ */
+void lv_table_set_row_cnt(lv_obj_t * obj, uint16_t row_cnt);
+
+/**
+ * Set the number of columns
+ * @param obj table pointer to a Table object
+ * @param col_cnt number of columns.
+ */
+void lv_table_set_col_cnt(lv_obj_t * obj, uint16_t col_cnt);
+
+/**
+ * Set the width of a column
+ * @param obj table pointer to a Table object
+ * @param col_id id of the column [0 .. LV_TABLE_COL_MAX -1]
+ * @param w width of the column
+ */
+void lv_table_set_col_width(lv_obj_t * obj, uint16_t col_id, lv_coord_t w);
+
+/**
+ * Add control bits to the cell.
+ * @param obj pointer to a Table object
+ * @param row id of the row [0 .. row_cnt -1]
+ * @param col id of the column [0 .. col_cnt -1]
+ * @param ctrl OR-ed values from ::lv_table_cell_ctrl_t
+ */
+void lv_table_add_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl);
+
+
+/**
+ * Clear control bits of the cell.
+ * @param obj pointer to a Table object
+ * @param row id of the row [0 .. row_cnt -1]
+ * @param col id of the column [0 .. col_cnt -1]
+ * @param ctrl OR-ed values from ::lv_table_cell_ctrl_t
+ */
+void lv_table_clear_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the value of a cell.
+ * @param obj pointer to a Table object
+ * @param row id of the row [0 .. row_cnt -1]
+ * @param col id of the column [0 .. col_cnt -1]
+ * @return text in the cell
+ */
+const char * lv_table_get_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col);
+
+/**
+ * Get the number of rows.
+ * @param obj table pointer to a Table object
+ * @return number of rows.
+ */
+uint16_t lv_table_get_row_cnt(lv_obj_t * obj);
+
+/**
+ * Get the number of columns.
+ * @param obj table pointer to a Table object
+ * @return number of columns.
+ */
+uint16_t lv_table_get_col_cnt(lv_obj_t * obj);
+
+/**
+ * Get the width of a column
+ * @param obj table pointer to a Table object
+ * @param col id of the column [0 .. LV_TABLE_COL_MAX -1]
+ * @return width of the column
+ */
+lv_coord_t lv_table_get_col_width(lv_obj_t * obj, uint16_t col);
+
+/**
+ * Get whether a cell has the control bits
+ * @param obj pointer to a Table object
+ * @param row id of the row [0 .. row_cnt -1]
+ * @param col id of the column [0 .. col_cnt -1]
+ * @param ctrl OR-ed values from ::lv_table_cell_ctrl_t
+ * @return true: all control bits are set; false: not all control bits are set
+ */
+bool lv_table_has_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl);
+
+/**
+ * Get the selected cell (pressed and or focused)
+ * @param obj pointer to a table object
+ * @param row pointer to variable to store the selected row (LV_TABLE_CELL_NONE: if no cell selected)
+ * @param col pointer to variable to store the selected column (LV_TABLE_CELL_NONE: if no cell selected)
+ */
+void lv_table_get_selected_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_TABLE*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_TABLE_H*/
diff --git a/lib/lvgl/src/widgets/lv_textarea.c b/lib/lvgl/src/widgets/lv_textarea.c
new file mode 100644
index 00000000..4d497e66
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_textarea.c
@@ -0,0 +1,1370 @@
+/**
+ * @file lv_ta.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_textarea.h"
+#if LV_USE_TEXTAREA != 0
+
+#include <string.h>
+#include "../misc/lv_assert.h"
+#include "../core/lv_group.h"
+#include "../core/lv_refr.h"
+#include "../core/lv_indev.h"
+#include "../draw/lv_draw.h"
+#include "../misc/lv_anim.h"
+#include "../misc/lv_txt.h"
+#include "../misc/lv_math.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define MY_CLASS &lv_textarea_class
+
+/*Test configuration*/
+#ifndef LV_TEXTAREA_DEF_CURSOR_BLINK_TIME
+ #define LV_TEXTAREA_DEF_CURSOR_BLINK_TIME 400 /*ms*/
+#endif
+
+#ifndef LV_TEXTAREA_DEF_PWD_SHOW_TIME
+ #define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
+#endif
+
+#define LV_TEXTAREA_PWD_BULLET_UNICODE 0x2022
+#define IGNORE_KERNING '\0'
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void lv_textarea_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_textarea_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
+static void lv_textarea_event(const lv_obj_class_t * class_p, lv_event_t * e);
+static void label_event_cb(lv_event_t * e);
+static void cursor_blink_anim_cb(void * obj, int32_t show);
+static void pwd_char_hider_anim(void * obj, int32_t x);
+static void pwd_char_hider_anim_ready(lv_anim_t * a);
+static void pwd_char_hider(lv_obj_t * obj);
+static bool char_is_accepted(lv_obj_t * obj, uint32_t c);
+static void start_cursor_blink(lv_obj_t * obj);
+static void refr_cursor_area(lv_obj_t * obj);
+static void update_cursor_position_on_click(lv_event_t * e);
+static lv_res_t insert_handler(lv_obj_t * obj, const char * txt);
+static void draw_placeholder(lv_event_t * e);
+static void draw_cursor(lv_event_t * e);
+static void auto_hide_characters(lv_obj_t * obj);
+static inline bool is_valid_but_non_printable_char(const uint32_t letter);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+const lv_obj_class_t lv_textarea_class = {
+ .constructor_cb = lv_textarea_constructor,
+ .destructor_cb = lv_textarea_destructor,
+ .event_cb = lv_textarea_event,
+ .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
+ .width_def = LV_DPI_DEF * 2,
+ .height_def = LV_DPI_DEF,
+ .instance_size = sizeof(lv_textarea_t),
+ .base_class = &lv_obj_class
+};
+
+static const char * ta_insert_replace;
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+lv_obj_t * lv_textarea_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/remove functions
+ *=====================*/
+
+void lv_textarea_add_char(lv_obj_t * obj, uint32_t c)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ if(ta->one_line && (c == '\n' || c == '\r')) {
+ LV_LOG_INFO("Text area: line break ignored in one-line mode");
+ return;
+ }
+
+ uint32_t u32_buf[2];
+ u32_buf[0] = c;
+ u32_buf[1] = 0;
+
+ const char * letter_buf = (char *)&u32_buf;
+
+#if LV_BIG_ENDIAN_SYSTEM
+ if(c != 0) while(*letter_buf == 0) ++letter_buf;
+#endif
+
+ lv_res_t res = insert_handler(obj, letter_buf);
+ if(res != LV_RES_OK) return;
+
+ uint32_t c_uni = _lv_txt_encoded_next((const char *)&c, NULL);
+
+ if(char_is_accepted(obj, c_uni) == false) {
+ LV_LOG_INFO("Character is not accepted by the text area (too long text or not in the accepted list)");
+ return;
+ }
+
+ if(ta->pwd_mode) pwd_char_hider(obj); /*Make sure all the current text contains only '*'*/
+
+ /*If the textarea is empty, invalidate it to hide the placeholder*/
+ if(ta->placeholder_txt) {
+ const char * txt = lv_label_get_text(ta->label);
+ if(txt[0] == '\0') lv_obj_invalidate(obj);
+ }
+
+ lv_label_ins_text(ta->label, ta->cursor.pos, letter_buf); /*Insert the character*/
+ lv_textarea_clear_selection(obj); /*Clear selection*/
+
+ if(ta->pwd_mode) {
+ /*+2: the new char + \0*/
+ size_t realloc_size = strlen(ta->pwd_tmp) + strlen(letter_buf) + 1;
+ ta->pwd_tmp = lv_mem_realloc(ta->pwd_tmp, realloc_size);
+ LV_ASSERT_MALLOC(ta->pwd_tmp);
+ if(ta->pwd_tmp == NULL) return;
+
+ _lv_txt_ins(ta->pwd_tmp, ta->cursor.pos, (const char *)letter_buf);
+
+ /*Auto hide characters*/
+ auto_hide_characters(obj);
+ }
+
+ /*Move the cursor after the new character*/
+ lv_textarea_set_cursor_pos(obj, lv_textarea_get_cursor_pos(obj) + 1);
+
+ lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
+}
+
+void lv_textarea_add_text(lv_obj_t * obj, const char * txt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(txt);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ if(ta->pwd_mode) pwd_char_hider(obj); /*Make sure all the current text contains only '*'*/
+
+ /*Add the character one-by-one if not all characters are accepted or there is character limit.*/
+ if(lv_textarea_get_accepted_chars(obj) || lv_textarea_get_max_length(obj)) {
+ uint32_t i = 0;
+ while(txt[i] != '\0') {
+ uint32_t c = _lv_txt_encoded_next(txt, &i);
+ lv_textarea_add_char(obj, _lv_txt_unicode_to_encoded(c));
+ }
+ return;
+ }
+
+ lv_res_t res = insert_handler(obj, txt);
+ if(res != LV_RES_OK) return;
+
+ /*If the textarea is empty, invalidate it to hide the placeholder*/
+ if(ta->placeholder_txt) {
+ const char * txt_act = lv_label_get_text(ta->label);
+ if(txt_act[0] == '\0') lv_obj_invalidate(obj);
+ }
+
+ /*Insert the text*/
+ lv_label_ins_text(ta->label, ta->cursor.pos, txt);
+ lv_textarea_clear_selection(obj);
+
+ if(ta->pwd_mode) {
+ size_t realloc_size = strlen(ta->pwd_tmp) + strlen(txt) + 1;
+ ta->pwd_tmp = lv_mem_realloc(ta->pwd_tmp, realloc_size);
+ LV_ASSERT_MALLOC(ta->pwd_tmp);
+ if(ta->pwd_tmp == NULL) return;
+
+ _lv_txt_ins(ta->pwd_tmp, ta->cursor.pos, txt);
+
+ /*Auto hide characters*/
+ auto_hide_characters(obj);
+ }
+
+ /*Move the cursor after the new text*/
+ lv_textarea_set_cursor_pos(obj, lv_textarea_get_cursor_pos(obj) + _lv_txt_get_encoded_length(txt));
+
+ lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
+}
+
+void lv_textarea_del_char(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ uint32_t cur_pos = ta->cursor.pos;
+
+ if(cur_pos == 0) return;
+
+ char del_buf[2] = {LV_KEY_DEL, '\0'};
+
+ lv_res_t res = insert_handler(obj, del_buf);
+ if(res != LV_RES_OK) return;
+
+ char * label_txt = lv_label_get_text(ta->label);
+
+ /*Delete a character*/
+ _lv_txt_cut(label_txt, ta->cursor.pos - 1, 1);
+
+ /*Refresh the label*/
+ lv_label_set_text(ta->label, label_txt);
+ lv_textarea_clear_selection(obj);
+
+ /*If the textarea became empty, invalidate it to hide the placeholder*/
+ if(ta->placeholder_txt) {
+ const char * txt = lv_label_get_text(ta->label);
+ if(txt[0] == '\0') lv_obj_invalidate(obj);
+ }
+
+ if(ta->pwd_mode) {
+ _lv_txt_cut(ta->pwd_tmp, ta->cursor.pos - 1, 1);
+
+ ta->pwd_tmp = lv_mem_realloc(ta->pwd_tmp, strlen(ta->pwd_tmp) + 1);
+ LV_ASSERT_MALLOC(ta->pwd_tmp);
+ if(ta->pwd_tmp == NULL) return;
+ }
+
+ /*Move the cursor to the place of the deleted character*/
+ lv_textarea_set_cursor_pos(obj, ta->cursor.pos - 1);
+
+ lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
+
+}
+
+void lv_textarea_del_char_forward(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ uint32_t cp = lv_textarea_get_cursor_pos(obj);
+ lv_textarea_set_cursor_pos(obj, cp + 1);
+ if(cp != lv_textarea_get_cursor_pos(obj)) lv_textarea_del_char(obj);
+}
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+void lv_textarea_set_text(lv_obj_t * obj, const char * txt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(txt);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ /*Clear the existing selection*/
+ lv_textarea_clear_selection(obj);
+
+ /*Add the character one-by-one if not all characters are accepted or there is character limit.*/
+ if(lv_textarea_get_accepted_chars(obj) || lv_textarea_get_max_length(obj)) {
+ lv_label_set_text(ta->label, "");
+ lv_textarea_set_cursor_pos(obj, LV_TEXTAREA_CURSOR_LAST);
+ if(ta->pwd_mode) {
+ ta->pwd_tmp[0] = '\0'; /*Clear the password too*/
+ }
+ uint32_t i = 0;
+ while(txt[i] != '\0') {
+ uint32_t c = _lv_txt_encoded_next(txt, &i);
+ lv_textarea_add_char(obj, _lv_txt_unicode_to_encoded(c));
+ }
+ }
+ else {
+ lv_label_set_text(ta->label, txt);
+ lv_textarea_set_cursor_pos(obj, LV_TEXTAREA_CURSOR_LAST);
+ }
+
+ /*If the textarea is empty, invalidate it to hide the placeholder*/
+ if(ta->placeholder_txt) {
+ const char * txt_act = lv_label_get_text(ta->label);
+ if(txt_act[0] == '\0') lv_obj_invalidate(obj);
+ }
+
+ if(ta->pwd_mode) {
+ ta->pwd_tmp = lv_mem_realloc(ta->pwd_tmp, strlen(txt) + 1);
+ LV_ASSERT_MALLOC(ta->pwd_tmp);
+ if(ta->pwd_tmp == NULL) return;
+ strcpy(ta->pwd_tmp, txt);
+
+ /*Auto hide characters*/
+ auto_hide_characters(obj);
+ }
+
+ lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
+}
+
+void lv_textarea_set_placeholder_text(lv_obj_t * obj, const char * txt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(txt);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ size_t txt_len = strlen(txt);
+ if((txt_len == 0) && (ta->placeholder_txt)) {
+ lv_mem_free(ta->placeholder_txt);
+ ta->placeholder_txt = NULL;
+ }
+ else {
+ /*Allocate memory for the placeholder_txt text*/
+ /*NOTE: Using special realloc behavior, malloc-like when data_p is NULL*/
+ ta->placeholder_txt = lv_mem_realloc(ta->placeholder_txt, txt_len + 1);
+ LV_ASSERT_MALLOC(ta->placeholder_txt);
+ if(ta->placeholder_txt == NULL) {
+ LV_LOG_ERROR("lv_textarea_set_placeholder_text: couldn't allocate memory for placeholder");
+ return;
+ }
+
+ strcpy(ta->placeholder_txt, txt);
+ ta->placeholder_txt[txt_len] = '\0';
+ }
+
+ lv_obj_invalidate(obj);
+}
+
+void lv_textarea_set_cursor_pos(lv_obj_t * obj, int32_t pos)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ if((uint32_t)ta->cursor.pos == (uint32_t)pos) return;
+
+ uint32_t len = _lv_txt_get_encoded_length(lv_label_get_text(ta->label));
+
+ if(pos < 0) pos = len + pos;
+
+ if(pos > (int32_t)len || pos == LV_TEXTAREA_CURSOR_LAST) pos = len;
+
+ ta->cursor.pos = pos;
+
+ /*Position the label to make the cursor visible*/
+ lv_obj_update_layout(obj);
+
+ lv_point_t cur_pos;
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_label_get_letter_pos(ta->label, pos, &cur_pos);
+
+ /*The text area needs to have it's final size to see if the cursor is out of the area or not*/
+
+ /*Check the top*/
+ lv_coord_t font_h = lv_font_get_line_height(font);
+ if(cur_pos.y < lv_obj_get_scroll_top(obj)) {
+ lv_obj_scroll_to_y(obj, cur_pos.y, LV_ANIM_ON);
+ }
+ /*Check the bottom*/
+ lv_coord_t h = lv_obj_get_content_height(obj);
+ if(cur_pos.y + font_h - lv_obj_get_scroll_top(obj) > h) {
+ lv_obj_scroll_to_y(obj, cur_pos.y - h + font_h, LV_ANIM_ON);
+ }
+
+ /*Check the left*/
+ if(cur_pos.x < lv_obj_get_scroll_left(obj)) {
+ lv_obj_scroll_to_x(obj, cur_pos.x, LV_ANIM_ON);
+ }
+ /*Check the right*/
+ lv_coord_t w = lv_obj_get_content_width(obj);
+ if(cur_pos.x + font_h - lv_obj_get_scroll_left(obj) > w) {
+ lv_obj_scroll_to_x(obj, cur_pos.x - w + font_h, LV_ANIM_ON);
+ }
+
+ ta->cursor.valid_x = cur_pos.x;
+
+ start_cursor_blink(obj);
+
+ refr_cursor_area(obj);
+}
+
+void lv_textarea_set_cursor_click_pos(lv_obj_t * obj, bool en)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ ta->cursor.click_pos = en ? 1U : 0U;
+}
+
+void lv_textarea_set_password_mode(lv_obj_t * obj, bool en)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ if(ta->pwd_mode == en) return;
+
+ ta->pwd_mode = en ? 1U : 0U;
+ /*Pwd mode is now enabled*/
+ if(en) {
+ char * txt = lv_label_get_text(ta->label);
+ size_t len = strlen(txt);
+
+ ta->pwd_tmp = lv_mem_alloc(len + 1);
+ LV_ASSERT_MALLOC(ta->pwd_tmp);
+ if(ta->pwd_tmp == NULL) return;
+
+ strcpy(ta->pwd_tmp, txt);
+
+ pwd_char_hider(obj);
+
+ lv_textarea_clear_selection(obj);
+ }
+ /*Pwd mode is now disabled*/
+ else {
+ lv_textarea_clear_selection(obj);
+ lv_label_set_text(ta->label, ta->pwd_tmp);
+ lv_mem_free(ta->pwd_tmp);
+ ta->pwd_tmp = NULL;
+ }
+
+ refr_cursor_area(obj);
+}
+
+void lv_textarea_set_password_bullet(lv_obj_t * obj, const char * bullet)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+ LV_ASSERT_NULL(bullet);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ if(!bullet && (ta->pwd_bullet)) {
+ lv_mem_free(ta->pwd_bullet);
+ ta->pwd_bullet = NULL;
+ }
+ else {
+ size_t txt_len = strlen(bullet);
+
+ /*Allocate memory for the pwd_bullet text*/
+ /*NOTE: Using special realloc behavior, malloc-like when data_p is NULL*/
+ ta->pwd_bullet = lv_mem_realloc(ta->pwd_bullet, txt_len + 1);
+ LV_ASSERT_MALLOC(ta->pwd_bullet);
+ if(ta->pwd_bullet == NULL) {
+ LV_LOG_ERROR("lv_textarea_set_password_bullet: couldn't allocate memory for bullet");
+ return;
+ }
+
+ strcpy(ta->pwd_bullet, bullet);
+ ta->pwd_bullet[txt_len] = '\0';
+ }
+
+ lv_obj_invalidate(obj);
+}
+
+void lv_textarea_set_one_line(lv_obj_t * obj, bool en)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ if(ta->one_line == en) return;
+
+ ta->one_line = en ? 1U : 0U;
+ lv_coord_t width = en ? LV_SIZE_CONTENT : lv_pct(100);
+ lv_coord_t min_width_value = en ? lv_pct(100) : 0;
+
+ lv_obj_set_width(ta->label, width);
+ lv_obj_set_style_min_width(ta->label, min_width_value, 0);
+
+ if(en) {
+ lv_obj_set_height(obj, LV_SIZE_CONTENT);
+ }
+ else {
+ lv_obj_remove_local_style_prop(obj, LV_STYLE_HEIGHT, LV_PART_MAIN);
+ }
+
+ lv_obj_scroll_to(obj, 0, 0, LV_ANIM_OFF);
+}
+
+void lv_textarea_set_accepted_chars(lv_obj_t * obj, const char * list)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ ta->accepted_chars = list;
+}
+
+void lv_textarea_set_max_length(lv_obj_t * obj, uint32_t num)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ ta->max_length = num;
+}
+
+void lv_textarea_set_insert_replace(lv_obj_t * obj, const char * txt)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ LV_UNUSED(obj);
+ ta_insert_replace = txt;
+}
+
+void lv_textarea_set_text_selection(lv_obj_t * obj, bool en)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+#if LV_LABEL_TEXT_SELECTION
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ ta->text_sel_en = en;
+
+ if(!en) lv_textarea_clear_selection(obj);
+#else
+ LV_UNUSED(obj); /*Unused*/
+ LV_UNUSED(en); /*Unused*/
+#endif
+}
+
+void lv_textarea_set_password_show_time(lv_obj_t * obj, uint16_t time)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ ta->pwd_show_time = time;
+}
+
+void lv_textarea_set_align(lv_obj_t * obj, lv_text_align_t align)
+{
+ LV_LOG_WARN("Deprecated: use the normal text_align style property instead");
+ lv_obj_set_style_text_align(obj, align, 0);
+
+ switch(align) {
+ default:
+ case LV_TEXT_ALIGN_LEFT:
+ lv_obj_align(lv_textarea_get_label(obj), LV_ALIGN_TOP_LEFT, 0, 0);
+ break;
+ case LV_TEXT_ALIGN_RIGHT:
+ lv_obj_align(lv_textarea_get_label(obj), LV_ALIGN_TOP_RIGHT, 0, 0);
+ break;
+ case LV_TEXT_ALIGN_CENTER:
+ lv_obj_align(lv_textarea_get_label(obj), LV_ALIGN_TOP_MID, 0, 0);
+ break;
+ }
+}
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+const char * lv_textarea_get_text(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ const char * txt;
+ if(ta->pwd_mode == 0) {
+ txt = lv_label_get_text(ta->label);
+ }
+ else {
+ txt = ta->pwd_tmp;
+ }
+
+ return txt;
+}
+
+const char * lv_textarea_get_placeholder_text(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ if(ta->placeholder_txt) return ta->placeholder_txt;
+ else return "";
+}
+
+lv_obj_t * lv_textarea_get_label(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ return ta->label;
+}
+
+uint32_t lv_textarea_get_cursor_pos(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ return ta->cursor.pos;
+}
+
+bool lv_textarea_get_cursor_click_pos(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ return ta->cursor.click_pos ? true : false;
+}
+
+bool lv_textarea_get_password_mode(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ return ta->pwd_mode == 1U;
+}
+
+const char * lv_textarea_get_password_bullet(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ if(ta->pwd_bullet) return ta->pwd_bullet;
+
+ lv_font_glyph_dsc_t g;
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+
+ /*If the textarea's font has the bullet character use it else fallback to "*"*/
+ if(lv_font_get_glyph_dsc(font, &g, LV_TEXTAREA_PWD_BULLET_UNICODE, 0))
+ return LV_SYMBOL_BULLET;
+ return "*";
+}
+
+bool lv_textarea_get_one_line(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ return ta->one_line == 1U;
+}
+
+const char * lv_textarea_get_accepted_chars(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ return ta->accepted_chars;
+}
+
+uint32_t lv_textarea_get_max_length(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ return ta->max_length;
+}
+
+bool lv_textarea_text_is_selected(const lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+#if LV_LABEL_TEXT_SELECTION
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ if((lv_label_get_text_selection_start(ta->label) != LV_DRAW_LABEL_NO_TXT_SEL ||
+ lv_label_get_text_selection_end(ta->label) != LV_DRAW_LABEL_NO_TXT_SEL)) {
+ return true;
+ }
+ else {
+ return false;
+ }
+#else
+ LV_UNUSED(obj); /*Unused*/
+ return false;
+#endif
+}
+
+bool lv_textarea_get_text_selection(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+#if LV_LABEL_TEXT_SELECTION
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ return ta->text_sel_en;
+#else
+ LV_UNUSED(obj); /*Unused*/
+ return false;
+#endif
+}
+
+uint16_t lv_textarea_get_password_show_time(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ return ta->pwd_show_time;
+}
+
+/*=====================
+ * Other functions
+ *====================*/
+
+void lv_textarea_clear_selection(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+#if LV_LABEL_TEXT_SELECTION
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ if(lv_label_get_text_selection_start(ta->label) != LV_DRAW_LABEL_NO_TXT_SEL ||
+ lv_label_get_text_selection_end(ta->label) != LV_DRAW_LABEL_NO_TXT_SEL) {
+ lv_label_set_text_sel_start(ta->label, LV_DRAW_LABEL_NO_TXT_SEL);
+ lv_label_set_text_sel_end(ta->label, LV_DRAW_LABEL_NO_TXT_SEL);
+ }
+#else
+ LV_UNUSED(obj); /*Unused*/
+#endif
+}
+
+void lv_textarea_cursor_right(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ uint32_t cp = lv_textarea_get_cursor_pos(obj);
+ cp++;
+ lv_textarea_set_cursor_pos(obj, cp);
+}
+
+void lv_textarea_cursor_left(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ uint32_t cp = lv_textarea_get_cursor_pos(obj);
+ if(cp > 0) {
+ cp--;
+ lv_textarea_set_cursor_pos(obj, cp);
+ }
+}
+
+void lv_textarea_cursor_down(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ lv_point_t pos;
+
+ /*Get the position of the current letter*/
+ lv_label_get_letter_pos(ta->label, lv_textarea_get_cursor_pos(obj), &pos);
+
+ /*Increment the y with one line and keep the valid x*/
+
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+ pos.y += font_h + line_space + 1;
+ pos.x = ta->cursor.valid_x;
+
+ /*Do not go below the last line*/
+ if(pos.y < lv_obj_get_height(ta->label)) {
+ /*Get the letter index on the new cursor position and set it*/
+ uint32_t new_cur_pos = lv_label_get_letter_on(ta->label, &pos);
+
+ lv_coord_t cur_valid_x_tmp = ta->cursor.valid_x; /*Cursor position set overwrites the valid position*/
+ lv_textarea_set_cursor_pos(obj, new_cur_pos);
+ ta->cursor.valid_x = cur_valid_x_tmp;
+ }
+}
+
+void lv_textarea_cursor_up(lv_obj_t * obj)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ lv_point_t pos;
+
+ /*Get the position of the current letter*/
+ lv_label_get_letter_pos(ta->label, lv_textarea_get_cursor_pos(obj), &pos);
+
+ /*Decrement the y with one line and keep the valid x*/
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t font_h = lv_font_get_line_height(font);
+ pos.y -= font_h + line_space - 1;
+ pos.x = ta->cursor.valid_x;
+
+ /*Get the letter index on the new cursor position and set it*/
+ uint32_t new_cur_pos = lv_label_get_letter_on(ta->label, &pos);
+ lv_coord_t cur_valid_x_tmp = ta->cursor.valid_x; /*Cursor position set overwrites the valid position*/
+ lv_textarea_set_cursor_pos(obj, new_cur_pos);
+ ta->cursor.valid_x = cur_valid_x_tmp;
+}
+
+/**********************
+ * STATIC FUNCTIONS
+ **********************/
+
+static void lv_textarea_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+ LV_TRACE_OBJ_CREATE("begin");
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ ta->pwd_mode = 0;
+ ta->pwd_tmp = NULL;
+ ta->pwd_bullet = NULL;
+ ta->pwd_show_time = LV_TEXTAREA_DEF_PWD_SHOW_TIME;
+ ta->accepted_chars = NULL;
+ ta->max_length = 0;
+ ta->cursor.show = 1;
+ /*It will be set to zero later (with zero value lv_textarea_set_cursor_pos(obj, 0); wouldn't do anything as there is no difference)*/
+ ta->cursor.pos = 1;
+ ta->cursor.click_pos = 1;
+ ta->cursor.valid_x = 0;
+ ta->one_line = 0;
+#if LV_LABEL_TEXT_SELECTION
+ ta->text_sel_en = 0;
+#endif
+ ta->label = NULL;
+ ta->placeholder_txt = NULL;
+
+ ta->label = lv_label_create(obj);
+ lv_obj_set_width(ta->label, lv_pct(100));
+ lv_label_set_text(ta->label, "");
+ lv_obj_add_event_cb(ta->label, label_event_cb, LV_EVENT_ALL, NULL);
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
+ lv_textarea_set_cursor_pos(obj, 0);
+
+ start_cursor_blink(obj);
+
+ LV_TRACE_OBJ_CREATE("finished");
+}
+
+static void lv_textarea_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
+{
+ LV_UNUSED(class_p);
+
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ if(ta->pwd_tmp != NULL) {
+ lv_mem_free(ta->pwd_tmp);
+ ta->pwd_tmp = NULL;
+ }
+ if(ta->pwd_bullet != NULL) {
+ lv_mem_free(ta->pwd_bullet);
+ ta->pwd_bullet = NULL;
+ }
+ if(ta->placeholder_txt != NULL) {
+ lv_mem_free(ta->placeholder_txt);
+ ta->placeholder_txt = NULL;
+ }
+}
+
+static void lv_textarea_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*/
+ 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_FOCUSED) {
+ start_cursor_blink(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)
+ lv_textarea_cursor_right(obj);
+ else if(c == LV_KEY_LEFT)
+ lv_textarea_cursor_left(obj);
+ else if(c == LV_KEY_UP)
+ lv_textarea_cursor_up(obj);
+ else if(c == LV_KEY_DOWN)
+ lv_textarea_cursor_down(obj);
+ else if(c == LV_KEY_BACKSPACE)
+ lv_textarea_del_char(obj);
+ else if(c == LV_KEY_DEL)
+ lv_textarea_del_char_forward(obj);
+ else if(c == LV_KEY_HOME)
+ lv_textarea_set_cursor_pos(obj, 0);
+ else if(c == LV_KEY_END)
+ lv_textarea_set_cursor_pos(obj, LV_TEXTAREA_CURSOR_LAST);
+ else if(c == LV_KEY_ENTER && lv_textarea_get_one_line(obj))
+ lv_event_send(obj, LV_EVENT_READY, NULL);
+ else {
+ lv_textarea_add_char(obj, c);
+ }
+ }
+ else if(code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING || code == LV_EVENT_PRESS_LOST ||
+ code == LV_EVENT_RELEASED) {
+ update_cursor_position_on_click(e);
+ }
+ else if(code == LV_EVENT_DRAW_MAIN) {
+ draw_placeholder(e);
+ }
+ else if(code == LV_EVENT_DRAW_POST) {
+ draw_cursor(e);
+ }
+}
+
+static void label_event_cb(lv_event_t * e)
+{
+ lv_event_code_t code = lv_event_get_code(e);
+ lv_obj_t * label = lv_event_get_target(e);
+ lv_obj_t * ta = lv_obj_get_parent(label);
+
+ if(code == LV_EVENT_STYLE_CHANGED || code == LV_EVENT_SIZE_CHANGED) {
+ lv_label_set_text(label, NULL);
+ refr_cursor_area(ta);
+ start_cursor_blink(ta);
+ }
+}
+
+
+
+/**
+ * Called to blink the cursor
+ * @param ta pointer to a text area
+ * @param hide 1: hide the cursor, 0: show it
+ */
+static void cursor_blink_anim_cb(void * obj, int32_t show)
+{
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ if(show != ta->cursor.show) {
+ ta->cursor.show = show ? 1U : 0U;
+ lv_area_t area_tmp;
+ lv_area_copy(&area_tmp, &ta->cursor.area);
+ area_tmp.x1 += ta->label->coords.x1;
+ area_tmp.y1 += ta->label->coords.y1;
+ area_tmp.x2 += ta->label->coords.x1;
+ area_tmp.y2 += ta->label->coords.y1;
+ lv_obj_invalidate_area(obj, &area_tmp);
+ }
+}
+
+/**
+ * Dummy function to animate char hiding in pwd mode.
+ * Does nothing, but a function is required in car hiding anim.
+ * (pwd_char_hider callback do the real job)
+ * @param ta unused
+ * @param x unused
+ */
+static void pwd_char_hider_anim(void * obj, int32_t x)
+{
+ LV_UNUSED(obj);
+ LV_UNUSED(x);
+}
+
+/**
+ * Call when an animation is ready to convert all characters to '*'
+ * @param a pointer to the animation
+ */
+static void pwd_char_hider_anim_ready(lv_anim_t * a)
+{
+ lv_obj_t * obj = a->var;
+ pwd_char_hider(obj);
+}
+
+/**
+ * Hide all characters (convert them to '*')
+ * @param ta pointer to text area object
+ */
+static void pwd_char_hider(lv_obj_t * obj)
+{
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ if(ta->pwd_mode == 0) {
+ return;
+ }
+
+ /* When ta->label is empty we get 0 back */
+ char * txt = lv_label_get_text(ta->label);
+ uint32_t enc_len = _lv_txt_get_encoded_length(txt);
+ if(enc_len == 0) return;
+
+ const char * bullet = lv_textarea_get_password_bullet(obj);
+ const size_t bullet_len = strlen(bullet);
+ char * txt_tmp = lv_mem_buf_get(enc_len * bullet_len + 1);
+
+ uint32_t i;
+ for(i = 0; i < enc_len; i++) {
+ lv_memcpy(&txt_tmp[i * bullet_len], bullet, bullet_len);
+ }
+ txt_tmp[i * bullet_len] = '\0';
+
+ lv_label_set_text(ta->label, txt_tmp);
+ lv_mem_buf_release(txt_tmp);
+
+ refr_cursor_area(obj);
+}
+
+/**
+ * Test a unicode character if it is accepted or not. Checks max length and accepted char list.
+ * @param ta pointer to a test area object
+ * @param c a unicode character
+ * @return true: accepted; false: rejected
+ */
+static bool char_is_accepted(lv_obj_t * obj, uint32_t c)
+{
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ /*Too many characters?*/
+ if(ta->max_length > 0 && _lv_txt_get_encoded_length(lv_textarea_get_text(obj)) >= ta->max_length) {
+ return false;
+ }
+
+ if(ta->accepted_chars == NULL || ta->accepted_chars[0] == '\0') return true;
+ /*Accepted character?*/
+ uint32_t i = 0;
+
+ while(ta->accepted_chars[i] != '\0') {
+ uint32_t a = _lv_txt_encoded_next(ta->accepted_chars, &i);
+ if(a == c) return true; /*Accepted*/
+ }
+
+ return false; /*The character wasn't in the list*/
+}
+
+static void start_cursor_blink(lv_obj_t * obj)
+{
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ uint32_t blink_time = lv_obj_get_style_anim_time(obj, LV_PART_CURSOR);
+ if(blink_time == 0) {
+ lv_anim_del(obj, cursor_blink_anim_cb);
+ ta->cursor.show = 1;
+ }
+ else {
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, ta);
+ lv_anim_set_exec_cb(&a, cursor_blink_anim_cb);
+ lv_anim_set_time(&a, blink_time);
+ lv_anim_set_playback_time(&a, blink_time);
+ lv_anim_set_values(&a, 1, 0);
+ lv_anim_set_path_cb(&a, lv_anim_path_step);
+ lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
+ lv_anim_start(&a);
+ }
+}
+
+static void refr_cursor_area(lv_obj_t * obj)
+{
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+
+ const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);
+ lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);
+
+ uint32_t cur_pos = lv_textarea_get_cursor_pos(obj);
+ const char * txt = lv_label_get_text(ta->label);
+
+ uint32_t byte_pos = _lv_txt_encoded_get_byte_id(txt, cur_pos);
+ uint32_t letter = _lv_txt_encoded_next(&txt[byte_pos], NULL);
+
+ /* Letter height and width */
+ const lv_coord_t letter_h = lv_font_get_line_height(font);
+ /*Set letter_w (set not 0 on non printable but valid chars)*/
+ uint32_t letter_space = letter;
+ if(is_valid_but_non_printable_char(letter)) {
+ letter_space = ' ';
+ }
+ lv_coord_t letter_w = lv_font_get_glyph_width(font, letter_space, IGNORE_KERNING);
+
+ lv_point_t letter_pos;
+ lv_label_get_letter_pos(ta->label, cur_pos, &letter_pos);
+
+ lv_text_align_t align = lv_obj_calculate_style_text_align(ta->label, LV_PART_MAIN, lv_label_get_text(ta->label));
+
+ /*If the cursor is out of the text (most right) draw it to the next line*/
+ if(((letter_pos.x + ta->label->coords.x1) + letter_w > ta->label->coords.x2) &&
+ (ta->one_line == 0 && align != LV_TEXT_ALIGN_RIGHT)) {
+
+ letter_pos.x = 0;
+ letter_pos.y += letter_h + line_space;
+
+ if(letter != '\0') {
+ byte_pos += _lv_txt_encoded_size(&txt[byte_pos]);
+ letter = _lv_txt_encoded_next(&txt[byte_pos], NULL);
+ }
+
+ uint32_t tmp = letter;
+ if(is_valid_but_non_printable_char(letter)) {
+ tmp = ' ';
+ }
+ letter_w = lv_font_get_glyph_width(font, tmp, IGNORE_KERNING);
+ }
+
+ /*Save the byte position. It is required to draw `LV_CURSOR_BLOCK`*/
+ ta->cursor.txt_byte_pos = byte_pos;
+
+ /*Calculate the cursor according to its type*/
+ lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_CURSOR);
+ lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_CURSOR) + border_width;
+ lv_coord_t bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_CURSOR) + border_width;
+ lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_CURSOR) + border_width;
+ lv_coord_t right = lv_obj_get_style_pad_right(obj, LV_PART_CURSOR) + border_width;
+
+ lv_area_t cur_area;
+ cur_area.x1 = letter_pos.x - left;
+ cur_area.y1 = letter_pos.y - top;
+ cur_area.x2 = letter_pos.x + right + letter_w - 1;
+ cur_area.y2 = letter_pos.y + bottom + letter_h - 1;
+
+ /*Save the new area*/
+ lv_area_t area_tmp;
+ lv_area_copy(&area_tmp, &ta->cursor.area);
+ area_tmp.x1 += ta->label->coords.x1;
+ area_tmp.y1 += ta->label->coords.y1;
+ area_tmp.x2 += ta->label->coords.x1;
+ area_tmp.y2 += ta->label->coords.y1;
+ lv_obj_invalidate_area(obj, &area_tmp);
+
+ lv_area_copy(&ta->cursor.area, &cur_area);
+
+ lv_area_copy(&area_tmp, &ta->cursor.area);
+ area_tmp.x1 += ta->label->coords.x1;
+ area_tmp.y1 += ta->label->coords.y1;
+ area_tmp.x2 += ta->label->coords.x1;
+ area_tmp.y2 += ta->label->coords.y1;
+ lv_obj_invalidate_area(obj, &area_tmp);
+}
+
+static void update_cursor_position_on_click(lv_event_t * e)
+{
+ lv_indev_t * click_source = lv_indev_get_act();
+ if(click_source == NULL) return;
+
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ if(ta->cursor.click_pos == 0) return;
+
+ if(lv_indev_get_type(click_source) == LV_INDEV_TYPE_KEYPAD ||
+ lv_indev_get_type(click_source) == LV_INDEV_TYPE_ENCODER) {
+ return;
+ }
+
+ lv_area_t label_coords;
+ lv_obj_get_coords(ta->label, &label_coords);
+
+ lv_point_t point_act, vect_act;
+ lv_indev_get_point(click_source, &point_act);
+ lv_indev_get_vect(click_source, &vect_act);
+
+ if(point_act.x < 0 || point_act.y < 0) return; /*Ignore event from keypad*/
+ lv_point_t rel_pos;
+ rel_pos.x = point_act.x - label_coords.x1;
+ rel_pos.y = point_act.y - label_coords.y1;
+
+ const lv_event_code_t code = lv_event_get_code(e);
+
+ lv_coord_t label_width = lv_obj_get_width(ta->label);
+ uint16_t char_id_at_click = 0;
+
+#if LV_LABEL_TEXT_SELECTION
+ lv_label_t * label_data = (lv_label_t *)ta->label;
+ bool click_outside_label = false;
+ /*Check if the click happened on the left side of the area outside the label*/
+ if(rel_pos.x < 0) {
+ char_id_at_click = 0;
+ click_outside_label = true;
+ }
+ /*Check if the click happened on the right side of the area outside the label*/
+ else if(rel_pos.x >= label_width) {
+ char_id_at_click = LV_TEXTAREA_CURSOR_LAST;
+ click_outside_label = true;
+ }
+ else {
+ char_id_at_click = lv_label_get_letter_on(ta->label, &rel_pos);
+ click_outside_label = !lv_label_is_char_under_pos(ta->label, &rel_pos);
+ }
+
+ if(ta->text_sel_en) {
+ if(!ta->text_sel_in_prog && !click_outside_label && code == LV_EVENT_PRESSED) {
+ /*Input device just went down. Store the selection start position*/
+ ta->sel_start = char_id_at_click;
+ ta->sel_end = LV_LABEL_TEXT_SELECTION_OFF;
+ ta->text_sel_in_prog = 1;
+ lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN);
+ }
+ else if(ta->text_sel_in_prog && code == LV_EVENT_PRESSING) {
+ /*Input device may be moving. Store the end position*/
+ ta->sel_end = char_id_at_click;
+ }
+ else if(ta->text_sel_in_prog && (code == LV_EVENT_PRESS_LOST || code == LV_EVENT_RELEASED)) {
+ /*Input device is released. Check if anything was selected.*/
+ lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN);
+ }
+ }
+
+ if(ta->text_sel_in_prog || code == LV_EVENT_PRESSED) lv_textarea_set_cursor_pos(obj, char_id_at_click);
+
+ if(ta->text_sel_in_prog) {
+ /*If the selected area has changed then update the real values and*/
+
+ /*Invalidate the text area.*/
+ if(ta->sel_start > ta->sel_end) {
+ if(label_data->sel_start != ta->sel_end || label_data->sel_end != ta->sel_start) {
+ label_data->sel_start = ta->sel_end;
+ label_data->sel_end = ta->sel_start;
+ lv_obj_invalidate(obj);
+ }
+ }
+ else if(ta->sel_start < ta->sel_end) {
+ if(label_data->sel_start != ta->sel_start || label_data->sel_end != ta->sel_end) {
+ label_data->sel_start = ta->sel_start;
+ label_data->sel_end = ta->sel_end;
+ lv_obj_invalidate(obj);
+ }
+ }
+ else {
+ if(label_data->sel_start != LV_DRAW_LABEL_NO_TXT_SEL || label_data->sel_end != LV_DRAW_LABEL_NO_TXT_SEL) {
+ label_data->sel_start = LV_DRAW_LABEL_NO_TXT_SEL;
+ label_data->sel_end = LV_DRAW_LABEL_NO_TXT_SEL;
+ lv_obj_invalidate(obj);
+ }
+ }
+ /*Finish selection if necessary*/
+ if(code == LV_EVENT_PRESS_LOST || code == LV_EVENT_RELEASED) {
+ ta->text_sel_in_prog = 0;
+ }
+ }
+#else
+ /*Check if the click happened on the left side of the area outside the label*/
+ if(rel_pos.x < 0) {
+ char_id_at_click = 0;
+ }
+ /*Check if the click happened on the right side of the area outside the label*/
+ else if(rel_pos.x >= label_width) {
+ char_id_at_click = LV_TEXTAREA_CURSOR_LAST;
+ }
+ else {
+ char_id_at_click = lv_label_get_letter_on(ta->label, &rel_pos);
+ }
+
+ if(code == LV_EVENT_PRESSED) lv_textarea_set_cursor_pos(obj, char_id_at_click);
+#endif
+}
+
+/* Returns LV_RES_OK when no operation were performed
+ * Returns LV_RES_INV when a user defined text was inserted */
+static lv_res_t insert_handler(lv_obj_t * obj, const char * txt)
+{
+ ta_insert_replace = NULL;
+ lv_event_send(obj, LV_EVENT_INSERT, (char *)txt);
+
+ /* Drop txt if insert replace is set to '\0' */
+ if(ta_insert_replace && ta_insert_replace[0] == '\0')
+ return LV_RES_INV;
+
+ if(ta_insert_replace) {
+ /*Add the replaced text directly it's different from the original*/
+ if(strcmp(ta_insert_replace, txt)) {
+ lv_textarea_add_text(obj, ta_insert_replace);
+ return LV_RES_INV;
+ }
+ }
+
+ return LV_RES_OK;
+}
+
+static void draw_placeholder(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+ const char * txt = lv_label_get_text(ta->label);
+
+ /*Draw the place holder*/
+ if(txt[0] == '\0' && ta->placeholder_txt && ta->placeholder_txt[0] != 0) {
+ lv_draw_label_dsc_t ph_dsc;
+ lv_draw_label_dsc_init(&ph_dsc);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_TEXTAREA_PLACEHOLDER, &ph_dsc);
+
+ if(ta->one_line) ph_dsc.flag |= LV_TEXT_FLAG_EXPAND;
+
+ lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+ lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
+ lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
+ lv_area_t ph_coords;
+ lv_area_copy(&ph_coords, &obj->coords);
+ lv_area_move(&ph_coords, left + border_width, top + border_width);
+ lv_draw_label(draw_ctx, &ph_dsc, &ph_coords, ta->placeholder_txt, NULL);
+ }
+}
+
+static void draw_cursor(lv_event_t * e)
+{
+ lv_obj_t * obj = lv_event_get_target(e);
+ lv_textarea_t * ta = (lv_textarea_t *)obj;
+ lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
+ const char * txt = lv_label_get_text(ta->label);
+
+ if(ta->cursor.show == 0) return;
+
+ lv_draw_rect_dsc_t cur_dsc;
+ lv_draw_rect_dsc_init(&cur_dsc);
+ lv_obj_init_draw_rect_dsc(obj, LV_PART_CURSOR, &cur_dsc);
+
+ /*Draw he cursor according to the type*/
+ lv_area_t cur_area;
+ lv_area_copy(&cur_area, &ta->cursor.area);
+
+
+ cur_area.x1 += ta->label->coords.x1;
+ cur_area.y1 += ta->label->coords.y1;
+ cur_area.x2 += ta->label->coords.x1;
+ cur_area.y2 += ta->label->coords.y1;
+
+ lv_draw_rect(draw_ctx, &cur_dsc, &cur_area);
+
+ lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_CURSOR);
+ lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_CURSOR) + border_width;
+ lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_CURSOR) + border_width;
+ char letter_buf[8] = {0};
+ lv_memcpy(letter_buf, &txt[ta->cursor.txt_byte_pos], _lv_txt_encoded_size(&txt[ta->cursor.txt_byte_pos]));
+
+ cur_area.x1 += left;
+ cur_area.y1 += top;
+
+ /*Draw the letter over the cursor only if
+ *the cursor has background or the letter has different color than the original.
+ *Else the original letter is drawn twice which makes it look bolder*/
+ lv_color_t label_color = lv_obj_get_style_text_color(ta->label, 0);
+ lv_draw_label_dsc_t cur_label_dsc;
+ lv_draw_label_dsc_init(&cur_label_dsc);
+ lv_obj_init_draw_label_dsc(obj, LV_PART_CURSOR, &cur_label_dsc);
+ if(cur_dsc.bg_opa > LV_OPA_MIN || cur_label_dsc.color.full != label_color.full) {
+ lv_draw_label(draw_ctx, &cur_label_dsc, &cur_area, letter_buf, NULL);
+ }
+}
+
+static void auto_hide_characters(lv_obj_t * obj)
+{
+ lv_textarea_t * ta = (lv_textarea_t *) obj;
+
+ if(ta->pwd_show_time == 0) {
+ pwd_char_hider(obj);
+ }
+ else {
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_var(&a, ta);
+ lv_anim_set_exec_cb(&a, pwd_char_hider_anim);
+ lv_anim_set_time(&a, ta->pwd_show_time);
+ lv_anim_set_values(&a, 0, 1);
+ lv_anim_set_path_cb(&a, lv_anim_path_step);
+ lv_anim_set_ready_cb(&a, pwd_char_hider_anim_ready);
+ lv_anim_start(&a);
+ }
+}
+
+static inline bool is_valid_but_non_printable_char(const uint32_t letter)
+{
+ if(letter == '\0' || letter == '\n' || letter == '\r') {
+ return true;
+ }
+
+ return false;
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/lv_textarea.h b/lib/lvgl/src/widgets/lv_textarea.h
new file mode 100644
index 00000000..4b3289b4
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_textarea.h
@@ -0,0 +1,358 @@
+/**
+ * @file lv_textarea.h
+ *
+ */
+
+#ifndef LV_TEXTAREA_H
+#define LV_TEXTAREA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../lv_conf_internal.h"
+
+#if LV_USE_TEXTAREA != 0
+
+/*Testing of dependencies*/
+#if LV_USE_LABEL == 0
+#error "lv_ta: lv_label is required. Enable it in lv_conf.h (LV_USE_LABEL 1)"
+#endif
+
+#include "../core/lv_obj.h"
+#include "lv_label.h"
+
+/*********************
+ * DEFINES
+ *********************/
+#define LV_TEXTAREA_CURSOR_LAST (0x7FFF) /*Put the cursor after the last character*/
+
+LV_EXPORT_CONST_INT(LV_TEXTAREA_CURSOR_LAST);
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/*Data of text area*/
+typedef struct {
+ lv_obj_t obj;
+ lv_obj_t * label; /*Label of the text area*/
+ char * placeholder_txt; /*Place holder label. only visible if text is an empty string*/
+ char * pwd_tmp; /*Used to store the original text in password mode*/
+ char * pwd_bullet; /*Replacement characters displayed in password mode*/
+ const char * accepted_chars; /*Only these characters will be accepted. NULL: accept all*/
+ uint32_t max_length; /*The max. number of characters. 0: no limit*/
+ uint16_t pwd_show_time; /*Time to show characters in password mode before change them to '*'*/
+ struct {
+ lv_coord_t valid_x; /*Used when stepping up/down to a shorter line.
+ *(Used by the library)*/
+ uint32_t pos; /*The current cursor position
+ *(0: before 1st letter; 1: before 2nd letter ...)*/
+ lv_area_t area; /*Cursor area relative to the Text Area*/
+ uint32_t txt_byte_pos; /*Byte index of the letter after (on) the cursor*/
+ uint8_t show : 1; /*Cursor is visible now or not (Handled by the library)*/
+ uint8_t click_pos : 1; /*1: Enable positioning the cursor by clicking the text area*/
+ } cursor;
+#if LV_LABEL_TEXT_SELECTION
+ uint32_t sel_start; /*Temporary values for text selection*/
+ uint32_t sel_end;
+ uint8_t text_sel_in_prog : 1; /*User is in process of selecting*/
+ uint8_t text_sel_en : 1; /*Text can be selected on this text area*/
+#endif
+ uint8_t pwd_mode : 1; /*Replace characters with '*'*/
+ uint8_t one_line : 1; /*One line mode (ignore line breaks)*/
+} lv_textarea_t;
+
+extern const lv_obj_class_t lv_textarea_class;
+
+enum {
+ LV_PART_TEXTAREA_PLACEHOLDER = LV_PART_CUSTOM_FIRST,
+};
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a text area object
+ * @param parent pointer to an object, it will be the parent of the new text area
+ * @return pointer to the created text area
+ */
+lv_obj_t * lv_textarea_create(lv_obj_t * parent);
+
+/*======================
+ * Add/remove functions
+ *=====================*/
+
+/**
+ * Insert a character to the current cursor position.
+ * To add a wide char, e.g. 'Á' use `_lv_txt_encoded_conv_wc('Á')`
+ * @param obj pointer to a text area object
+ * @param c a character (e.g. 'a')
+ */
+void lv_textarea_add_char(lv_obj_t * obj, uint32_t c);
+
+/**
+ * Insert a text to the current cursor position
+ * @param obj pointer to a text area object
+ * @param txt a '\0' terminated string to insert
+ */
+void lv_textarea_add_text(lv_obj_t * obj, const char * txt);
+
+/**
+ * Delete a the left character from the current cursor position
+ * @param obj pointer to a text area object
+ */
+void lv_textarea_del_char(lv_obj_t * obj);
+
+/**
+ * Delete the right character from the current cursor position
+ * @param obj pointer to a text area object
+ */
+void lv_textarea_del_char_forward(lv_obj_t * obj);
+
+/*=====================
+ * Setter functions
+ *====================*/
+
+/**
+ * Set the text of a text area
+ * @param obj pointer to a text area object
+ * @param txt pointer to the text
+ */
+void lv_textarea_set_text(lv_obj_t * obj, const char * txt);
+
+/**
+ * Set the placeholder text of a text area
+ * @param obj pointer to a text area object
+ * @param txt pointer to the text
+ */
+void lv_textarea_set_placeholder_text(lv_obj_t * obj, const char * txt);
+
+/**
+ * Set the cursor position
+ * @param obj pointer to a text area object
+ * @param pos the new cursor position in character index
+ * < 0 : index from the end of the text
+ * LV_TEXTAREA_CURSOR_LAST: go after the last character
+ */
+void lv_textarea_set_cursor_pos(lv_obj_t * obj, int32_t pos);
+
+/**
+ * Enable/Disable the positioning of the cursor by clicking the text on the text area.
+ * @param obj pointer to a text area object
+ * @param en true: enable click positions; false: disable
+ */
+void lv_textarea_set_cursor_click_pos(lv_obj_t * obj, bool en);
+
+/**
+ * Enable/Disable password mode
+ * @param obj pointer to a text area object
+ * @param en true: enable, false: disable
+ */
+void lv_textarea_set_password_mode(lv_obj_t * obj, bool en);
+
+/**
+ * Set the replacement characters to show in password mode
+ * @param obj pointer to a text area object
+ * @param bullet pointer to the replacement text
+ */
+void lv_textarea_set_password_bullet(lv_obj_t * obj, const char * bullet);
+
+/**
+ * Configure the text area to one line or back to normal
+ * @param obj pointer to a text area object
+ * @param en true: one line, false: normal
+ */
+void lv_textarea_set_one_line(lv_obj_t * obj, bool en);
+
+/**
+ * Set a list of characters. Only these characters will be accepted by the text area
+ * @param obj pointer to a text area object
+ * @param list list of characters. Only the pointer is saved. E.g. "+-.,0123456789"
+ */
+void lv_textarea_set_accepted_chars(lv_obj_t * obj, const char * list);
+
+/**
+ * Set max length of a Text Area.
+ * @param obj pointer to a text area object
+ * @param num the maximal number of characters can be added (`lv_textarea_set_text` ignores it)
+ */
+void lv_textarea_set_max_length(lv_obj_t * obj, uint32_t num);
+
+/**
+ * In `LV_EVENT_INSERT` the text which planned to be inserted can be replaced by an other text.
+ * It can be used to add automatic formatting to the text area.
+ * @param obj pointer to a text area object
+ * @param txt pointer to a new string to insert. If `""` no text will be added.
+ * The variable must be live after the `event_cb` exists. (Should be `global` or `static`)
+ */
+void lv_textarea_set_insert_replace(lv_obj_t * obj, const char * txt);
+
+/**
+ * Enable/disable selection mode.
+ * @param obj pointer to a text area object
+ * @param en true or false to enable/disable selection mode
+ */
+void lv_textarea_set_text_selection(lv_obj_t * obj, bool en);
+
+/**
+ * Set how long show the password before changing it to '*'
+ * @param obj pointer to a text area object
+ * @param time show time in milliseconds. 0: hide immediately.
+ */
+void lv_textarea_set_password_show_time(lv_obj_t * obj, uint16_t time);
+
+/**
+ * Deprecated: use the normal text_align style property instead
+ * Set the label's alignment.
+ * It sets where the label is aligned (in one line mode it can be smaller than the text area)
+ * and how the lines of the area align in case of multiline text area
+ * @param obj pointer to a text area object
+ * @param align the align mode from ::lv_text_align_t
+ */
+void lv_textarea_set_align(lv_obj_t * obj, lv_text_align_t align);
+
+/*=====================
+ * Getter functions
+ *====================*/
+
+/**
+ * Get the text of a text area. In password mode it gives the real text (not '*'s).
+ * @param obj pointer to a text area object
+ * @return pointer to the text
+ */
+const char * lv_textarea_get_text(const lv_obj_t * obj);
+
+/**
+ * Get the placeholder text of a text area
+ * @param obj pointer to a text area object
+ * @return pointer to the text
+ */
+const char * lv_textarea_get_placeholder_text(lv_obj_t * obj);
+
+/**
+ * Get the label of a text area
+ * @param obj pointer to a text area object
+ * @return pointer to the label object
+ */
+lv_obj_t * lv_textarea_get_label(const lv_obj_t * obj);
+
+/**
+ * Get the current cursor position in character index
+ * @param obj pointer to a text area object
+ * @return the cursor position
+ */
+uint32_t lv_textarea_get_cursor_pos(const lv_obj_t * obj);
+
+/**
+ * Get whether the cursor click positioning is enabled or not.
+ * @param obj pointer to a text area object
+ * @return true: enable click positions; false: disable
+ */
+bool lv_textarea_get_cursor_click_pos(lv_obj_t * obj);
+
+/**
+ * Get the password mode attribute
+ * @param obj pointer to a text area object
+ * @return true: password mode is enabled, false: disabled
+ */
+bool lv_textarea_get_password_mode(const lv_obj_t * obj);
+
+/**
+ * Get the replacement characters to show in password mode
+ * @param obj pointer to a text area object
+ * @return pointer to the replacement text
+ */
+const char * lv_textarea_get_password_bullet(lv_obj_t * obj);
+
+/**
+ * Get the one line configuration attribute
+ * @param obj pointer to a text area object
+ * @return true: one line configuration is enabled, false: disabled
+ */
+bool lv_textarea_get_one_line(const lv_obj_t * obj);
+
+/**
+ * Get a list of accepted characters.
+ * @param obj pointer to a text area object
+ * @return list of accented characters.
+ */
+const char * lv_textarea_get_accepted_chars(lv_obj_t * obj);
+
+/**
+ * Get max length of a Text Area.
+ * @param obj pointer to a text area object
+ * @return the maximal number of characters to be add
+ */
+uint32_t lv_textarea_get_max_length(lv_obj_t * obj);
+
+/**
+ * Find whether text is selected or not.
+ * @param obj pointer to a text area object
+ * @return whether text is selected or not
+ */
+bool lv_textarea_text_is_selected(const lv_obj_t * obj);
+
+/**
+ * Find whether selection mode is enabled.
+ * @param obj pointer to a text area object
+ * @return true: selection mode is enabled, false: disabled
+ */
+bool lv_textarea_get_text_selection(lv_obj_t * obj);
+
+/**
+ * Set how long show the password before changing it to '*'
+ * @param obj pointer to a text area object
+ * @return show time in milliseconds. 0: hide immediately.
+ */
+uint16_t lv_textarea_get_password_show_time(lv_obj_t * obj);
+
+/*=====================
+ * Other functions
+ *====================*/
+
+/**
+ * Clear the selection on the text area.
+ * @param obj pointer to a text area object
+ */
+void lv_textarea_clear_selection(lv_obj_t * obj);
+
+/**
+ * Move the cursor one character right
+ * @param obj pointer to a text area object
+ */
+void lv_textarea_cursor_right(lv_obj_t * obj);
+
+/**
+ * Move the cursor one character left
+ * @param obj pointer to a text area object
+ */
+void lv_textarea_cursor_left(lv_obj_t * obj);
+
+/**
+ * Move the cursor one line down
+ * @param obj pointer to a text area object
+ */
+void lv_textarea_cursor_down(lv_obj_t * obj);
+
+/**
+ * Move the cursor one line up
+ * @param obj pointer to a text area object
+ */
+void lv_textarea_cursor_up(lv_obj_t * obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_TEXTAREA_H*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_TEXTAREA_H*/
diff --git a/lib/lvgl/src/widgets/lv_widgets.mk b/lib/lvgl/src/widgets/lv_widgets.mk
new file mode 100644
index 00000000..4e62dc58
--- /dev/null
+++ b/lib/lvgl/src/widgets/lv_widgets.mk
@@ -0,0 +1,20 @@
+CSRCS += lv_arc.c
+CSRCS += lv_bar.c
+CSRCS += lv_btn.c
+CSRCS += lv_btnmatrix.c
+CSRCS += lv_canvas.c
+CSRCS += lv_checkbox.c
+CSRCS += lv_dropdown.c
+CSRCS += lv_img.c
+CSRCS += lv_label.c
+CSRCS += lv_line.c
+CSRCS += lv_roller.c
+CSRCS += lv_slider.c
+CSRCS += lv_switch.c
+CSRCS += lv_table.c
+CSRCS += lv_textarea.c
+
+DEPPATH += --dep-path $(LVGL_DIR)/$(LVGL_DIR_NAME)/src/widgets
+VPATH += :$(LVGL_DIR)/$(LVGL_DIR_NAME)/src/widgets
+
+CFLAGS += "-I$(LVGL_DIR)/$(LVGL_DIR_NAME)/src/widgets"