diff --git a/env_support/cmsis-pack/LVGL.lvgl.1.0.8-alpha1.pack b/env_support/cmsis-pack/LVGL.lvgl.1.0.8-alpha1.pack new file mode 100644 index 000000000..ca055acce Binary files /dev/null and b/env_support/cmsis-pack/LVGL.lvgl.1.0.8-alpha1.pack differ diff --git a/src/widgets/spinbox/lv_spinbox.c b/src/widgets/spinbox/lv_spinbox.c index 88cbadeb7..d3afe1fca 100644 --- a/src/widgets/spinbox/lv_spinbox.c +++ b/src/widgets/spinbox/lv_spinbox.c @@ -16,6 +16,8 @@ * DEFINES *********************/ #define MY_CLASS &lv_spinbox_class +#define LV_SPINBOX_MAX_DIGIT_COUNT_WITH_8BYTES (LV_SPINBOX_MAX_DIGIT_COUNT + 8U) +#define LV_SPINBOX_MAX_DIGIT_COUNT_WITH_4BYTES (LV_SPINBOX_MAX_DIGIT_COUNT + 4U) /********************** * TYPEDEFS @@ -108,9 +110,9 @@ void lv_spinbox_set_digit_format(lv_obj_t * obj, uint8_t digit_count, uint8_t se if(separator_position >= digit_count) separator_position = 0; if(digit_count < LV_SPINBOX_MAX_DIGIT_COUNT) { - int64_t max_val = lv_pow(10, digit_count); + const int64_t max_val = lv_pow(10, digit_count); if(spinbox->range_max > max_val - 1) spinbox->range_max = max_val - 1; - if(spinbox->range_min < - max_val + 1) spinbox->range_min = - max_val + 1; + if(spinbox->range_min < -max_val + 1) spinbox->range_min = -max_val + 1; } spinbox->digit_count = digit_count; @@ -162,9 +164,10 @@ void lv_spinbox_set_cursor_pos(lv_obj_t * obj, uint8_t pos) { LV_ASSERT_OBJ(obj, MY_CLASS); lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; - int32_t step_limit; - step_limit = LV_MAX(spinbox->range_max, (spinbox->range_min < 0 ? (-spinbox->range_min) : spinbox->range_min)); - int32_t new_step = lv_pow(10, pos); + + const int32_t step_limit = LV_MAX(spinbox->range_max, LV_ABS(spinbox->range_min)); + const int32_t new_step = lv_pow(10, pos); + if(pos <= 0) spinbox->step = 1; else if(new_step <= step_limit) spinbox->step = new_step; @@ -226,11 +229,8 @@ void lv_spinbox_step_next(lv_obj_t * obj) LV_ASSERT_OBJ(obj, MY_CLASS); lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; - int32_t new_step = spinbox->step / 10; - if((new_step) > 0) - spinbox->step = new_step; - else - spinbox->step = 1; + const int32_t new_step = spinbox->step / 10; + spinbox->step = (new_step > 0) ? new_step : 1; lv_spinbox_updatevalue(obj); } @@ -243,9 +243,9 @@ void lv_spinbox_step_prev(lv_obj_t * obj) { LV_ASSERT_OBJ(obj, MY_CLASS); lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; - int32_t step_limit; - step_limit = LV_MAX(spinbox->range_max, (spinbox->range_min < 0 ? (-spinbox->range_min) : spinbox->range_min)); - int32_t new_step = spinbox->step * 10; + + const int32_t step_limit = LV_MAX(spinbox->range_max, LV_ABS(spinbox->range_min)); + const int32_t new_step = spinbox->step * 10; if(new_step <= step_limit) spinbox->step = new_step; lv_spinbox_updatevalue(obj); @@ -367,34 +367,32 @@ static void lv_spinbox_event(const lv_obj_class_t * class_p, lv_event_t * e) res = lv_obj_event_base(MY_CLASS, e); if(res != LV_RES_OK) return; - lv_event_code_t code = lv_event_get_code(e); + const lv_event_code_t code = lv_event_get_code(e); lv_obj_t * obj = lv_event_get_target(e); lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; if(code == LV_EVENT_RELEASED) { /*If released with an ENCODER then move to the next digit*/ lv_indev_t * indev = lv_indev_get_act(); - if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) { - if(lv_group_get_editing(lv_obj_get_group(obj))) { - if(spinbox->digit_count > 1) { - if(spinbox->digit_step_dir == LV_DIR_RIGHT) { - if(spinbox->step > 1) { - lv_spinbox_step_next(obj); - } - else { - /*Restart from the MSB*/ - spinbox->step = lv_pow(10, spinbox->digit_count - 2); - lv_spinbox_step_prev(obj); - } + if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER && lv_group_get_editing(lv_obj_get_group(obj))) { + if(spinbox->digit_count > 1) { + if(spinbox->digit_step_dir == LV_DIR_RIGHT) { + if(spinbox->step > 1) { + lv_spinbox_step_next(obj); } else { - if(spinbox->step < lv_pow(10, spinbox->digit_count - 1)) { - lv_spinbox_step_prev(obj); - } - else { - /*Restart from the LSB*/ - spinbox->step = 10; - lv_spinbox_step_next(obj); - } + /*Restart from the MSB*/ + spinbox->step = lv_pow(10, spinbox->digit_count - 2); + lv_spinbox_step_prev(obj); + } + } + else { + if(spinbox->step < lv_pow(10, spinbox->digit_count - 1)) { + lv_spinbox_step_prev(obj); + } + else { + /*Restart from the LSB*/ + spinbox->step = 10; + lv_spinbox_step_next(obj); } } } @@ -403,22 +401,27 @@ static void lv_spinbox_event(const lv_obj_class_t * class_p, lv_event_t * e) * Set `step` accordingly*/ else { const char * txt = lv_textarea_get_text(obj); - size_t txt_len = strlen(txt); + const size_t txt_len = strlen(txt); + /* Check cursor position */ + /* Cursor is in '.' digit */ if(txt[spinbox->ta.cursor.pos] == '.') { lv_textarea_cursor_left(obj); } + /* Cursor is already in the right-most digit */ else if(spinbox->ta.cursor.pos == (uint32_t)txt_len) { lv_textarea_set_cursor_pos(obj, txt_len - 1); } + /* Cursor is already in the left-most digit AND range_min is negative */ else if(spinbox->ta.cursor.pos == 0 && spinbox->range_min < 0) { lv_textarea_set_cursor_pos(obj, 1); } - size_t len = spinbox->digit_count - 1; + /* Handle spinbox with decimal point (spinbox->dec_point_pos != 0) */ uint16_t cp = spinbox->ta.cursor.pos; - if(spinbox->ta.cursor.pos > spinbox->dec_point_pos && spinbox->dec_point_pos != 0) cp--; + + const size_t len = spinbox->digit_count - 1; uint32_t pos = len - cp; if(spinbox->range_min < 0) pos++; @@ -460,11 +463,11 @@ static void lv_spinbox_updatevalue(lv_obj_t * obj) { lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; - char buf[LV_SPINBOX_MAX_DIGIT_COUNT + 8]; - lv_memzero(buf, sizeof(buf)); - char * buf_p = buf; - uint8_t cur_shift_left = 0; + /* LV_SPINBOX_MAX_DIGIT_COUNT_WITH_8BYTES (18): Max possible digit_count value (15) + sign + decimal point + NULL terminator */ + char textarea_txt[LV_SPINBOX_MAX_DIGIT_COUNT_WITH_8BYTES] = {0U}; + char * buf_p = textarea_txt; + uint8_t cur_shift_left = 0; if(spinbox->range_min < 0) { // hide sign if there are only positive values /*Add the sign*/ (*buf_p) = spinbox->value >= 0 ? '+' : '-'; @@ -475,33 +478,34 @@ static void lv_spinbox_updatevalue(lv_obj_t * obj) cur_shift_left++; } - int32_t i; - char digits[LV_SPINBOX_MAX_DIGIT_COUNT + 4]; /*Convert the numbers to string (the sign is already handled so always covert positive number)*/ - lv_snprintf(digits, sizeof(digits), "%" LV_PRId32, LV_ABS(spinbox->value)); + char digits[LV_SPINBOX_MAX_DIGIT_COUNT_WITH_4BYTES]; + lv_snprintf(digits, LV_SPINBOX_MAX_DIGIT_COUNT_WITH_4BYTES, "%" LV_PRId32, LV_ABS(spinbox->value)); /*Add leading zeros*/ - int lz_cnt = spinbox->digit_count - (int)strlen(digits); - if(lz_cnt > 0) { - for(i = (uint16_t)strlen(digits); i >= 0; i--) { - digits[i + lz_cnt] = digits[i]; + int32_t i; + const int digits_len = (int)strlen(digits); + + const int leading_zeros_cnt = spinbox->digit_count - digits_len; + if(leading_zeros_cnt) { + for(i = (uint16_t) digits_len; i >= 0; i--) { + digits[i + leading_zeros_cnt] = digits[i]; } - for(i = 0; i < lz_cnt; i++) { + /* NOTE: Substitute with memset? */ + for(i = 0; i < leading_zeros_cnt; i++) { digits[i] = '0'; } } - int32_t intDigits; - intDigits = (spinbox->dec_point_pos == 0) ? spinbox->digit_count : spinbox->dec_point_pos; - /*Add the decimal part*/ + const int32_t intDigits = (spinbox->dec_point_pos == 0) ? spinbox->digit_count : spinbox->dec_point_pos; for(i = 0; i < intDigits && digits[i] != '\0'; i++) { (*buf_p) = digits[i]; buf_p++; } - if(spinbox->dec_point_pos != 0) { - /*Insert the decimal point*/ + /*Insert the decimal point*/ + if(spinbox->dec_point_pos) { (*buf_p) = '.'; buf_p++; @@ -512,10 +516,10 @@ static void lv_spinbox_updatevalue(lv_obj_t * obj) } /*Refresh the text*/ - lv_textarea_set_text(obj, (char *)buf); + lv_textarea_set_text(obj, (char *)textarea_txt); /*Set the cursor position*/ - int32_t step = spinbox->step; + int32_t step = spinbox->step; uint8_t cur_pos = (uint8_t)spinbox->digit_count; while(step >= 10) { step /= 10; diff --git a/tests/src/test_cases/test_spinbox.c b/tests/src/test_cases/test_spinbox.c new file mode 100644 index 000000000..09033b859 --- /dev/null +++ b/tests/src/test_cases/test_spinbox.c @@ -0,0 +1,308 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" + +#include "unity/unity.h" +#include "lv_test_indev.h" + +static lv_obj_t * active_screen = NULL; +static lv_obj_t * spinbox_negative_min_range = NULL; +static lv_obj_t * spinbox_zero_min_range = NULL; +static lv_obj_t * spinbox_events = NULL; +static lv_group_t * g = NULL; + +static const int32_t SPINBOX_NEGATIVE_MIN_RANGE_VALUE = -11; +static const int32_t SPINBOX_ZERO_MIN_RANGE_VALUE = 0; +static const int32_t SPINBOX_NEGATIVE_MAX_RANGE_VALUE = 12; +static const uint8_t SPINBOX_DECIMAL_POSITION = 1U; + +void setUp(void) +{ + active_screen = lv_scr_act(); + spinbox_negative_min_range = lv_spinbox_create(active_screen); + spinbox_zero_min_range = lv_spinbox_create(active_screen); + spinbox_events = lv_spinbox_create(active_screen); + + lv_spinbox_set_range(spinbox_negative_min_range, SPINBOX_NEGATIVE_MIN_RANGE_VALUE, SPINBOX_NEGATIVE_MAX_RANGE_VALUE); + lv_spinbox_set_range(spinbox_zero_min_range, SPINBOX_ZERO_MIN_RANGE_VALUE, SPINBOX_NEGATIVE_MAX_RANGE_VALUE); + + g = lv_group_create(); + lv_indev_set_group(lv_test_encoder_indev, g); +} + +void tearDown(void) +{ + lv_group_remove_obj(spinbox_events); + + lv_obj_del(spinbox_negative_min_range); + lv_obj_del(spinbox_zero_min_range); + lv_obj_del(spinbox_events); + + lv_obj_clean(active_screen); +} + +/* See issue #3559 for more info */ +void test_spinbox_decrement_when_min_range_is_negative(void) +{ + /* Current spinbox value is 2 */ + const int32_t expected_value = -11; + lv_spinbox_set_value(spinbox_negative_min_range, 2); + + /* Change cursor position of spinbox to 10 */ + lv_spinbox_set_cursor_pos(spinbox_negative_min_range, SPINBOX_DECIMAL_POSITION); + lv_spinbox_decrement(spinbox_negative_min_range); + lv_spinbox_decrement(spinbox_negative_min_range); + + /* We expect value now being -11 */ + int32_t actual_value = lv_spinbox_get_value(spinbox_negative_min_range); + + TEST_ASSERT_EQUAL_INT32(expected_value, actual_value); +} + +void test_spinbox_decrement_when_min_range_is_zero(void) +{ + /* Current spinbox value is 2 */ + const int32_t expected_value = 0; + lv_spinbox_set_value(spinbox_zero_min_range, 2); + + /* Change cursor position of spinbox to 10 */ + lv_spinbox_set_cursor_pos(spinbox_zero_min_range, SPINBOX_DECIMAL_POSITION); + lv_spinbox_decrement(spinbox_zero_min_range); + lv_spinbox_decrement(spinbox_zero_min_range); + + /* We expect value now being 0 */ + int32_t actual_value = lv_spinbox_get_value(spinbox_zero_min_range); + + TEST_ASSERT_EQUAL_INT32(expected_value, actual_value); +} + +void test_spinbox_position_selection(void) +{ + /* Assert step is 1 when selecting the lowest possible position */ + lv_spinbox_set_cursor_pos(spinbox_zero_min_range, 0); + TEST_ASSERT_EQUAL(1, lv_spinbox_get_step(spinbox_zero_min_range)); + + /* The other branch in the if */ + lv_spinbox_set_cursor_pos(spinbox_zero_min_range, 1); + TEST_ASSERT_EQUAL(10, lv_spinbox_get_step(spinbox_zero_min_range)); + + /* When not possible to select the indicated position */ + lv_obj_t * tmp; + tmp = lv_spinbox_create(active_screen); + lv_spinbox_set_range(tmp, 0, 10); + lv_spinbox_set_cursor_pos(tmp, 2); + TEST_ASSERT_EQUAL(1, lv_spinbox_get_step(tmp)); + lv_obj_clean(tmp); +} + +void test_spinbox_set_range(void) +{ + int32_t range_max = 40; + int32_t range_min = 20; + + lv_obj_t * tmp; + tmp = lv_spinbox_create(active_screen); + lv_spinbox_set_range(tmp, 0, 100); + lv_spinbox_set_value(tmp, 50); + + /* Validate value gets updated when range_max is smaller */ + lv_spinbox_set_range(tmp, 0, range_max); + + TEST_ASSERT_EQUAL(range_max, lv_spinbox_get_value(tmp)); + + /* Validate value gets updated when range_min is bigger */ + lv_spinbox_set_value(tmp, 5); + lv_spinbox_set_range(tmp, range_min, range_max); + + TEST_ASSERT_EQUAL(range_min, lv_spinbox_get_value(tmp)); + + lv_obj_clean(tmp); +} + +void test_spinbox_step_prev(void) +{ + lv_obj_t * tmp = lv_spinbox_create(active_screen); + + /* When next step is bigger than biggest range */ + lv_spinbox_set_range(tmp, 0, 5); + lv_spinbox_step_prev(tmp); + TEST_ASSERT_EQUAL(1, lv_spinbox_get_step(tmp)); + + lv_spinbox_step_next(tmp); + /* When next step is smaller than range_max */ + lv_spinbox_set_range(tmp, 0, 20); + lv_spinbox_step_prev(tmp); + TEST_ASSERT_EQUAL(10, lv_spinbox_get_step(tmp)); + + lv_spinbox_step_next(tmp); + /* When next step is smaller than abs(range_min) */ + lv_spinbox_set_range(tmp, -25, 5); + lv_spinbox_step_prev(tmp); + TEST_ASSERT_EQUAL(10, lv_spinbox_get_step(tmp)); + + lv_obj_clean(tmp); +} + +void test_spinbox_rollover(void) +{ + lv_obj_t * tmp = lv_spinbox_create(active_screen); + + lv_spinbox_set_rollover(tmp, true); + TEST_ASSERT_TRUE(lv_spinbox_get_rollover(tmp)); + + lv_spinbox_set_rollover(tmp, false); + TEST_ASSERT_FALSE(lv_spinbox_get_rollover(tmp)); + + lv_obj_clean(tmp); +} + +void test_spinbox_event_key(void) +{ + /* Spinbox should increment it's value by one after receiving the LV_KEY_UP event */ + lv_spinbox_set_value(spinbox_events, 0); + uint32_t key = LV_KEY_UP; + lv_event_send(spinbox_events, LV_EVENT_KEY, (void *) &key); + + TEST_ASSERT_EQUAL(1, lv_spinbox_get_value(spinbox_events)); + + /* Spinbox should decrement it's value by one after receiving the LV_KEY_DOWN event */ + key = LV_KEY_DOWN; + lv_event_send(spinbox_events, LV_EVENT_KEY, (void *) &key); + + TEST_ASSERT_EQUAL(0, lv_spinbox_get_value(spinbox_events)); + + /* Spinbox should multiply it's step vale by 10 after receiving the LV_KEY_LEFT event */ + int32_t step = lv_spinbox_get_step(spinbox_events); + key = LV_KEY_LEFT; + lv_event_send(spinbox_events, LV_EVENT_KEY, (void *) &key); + + TEST_ASSERT_EQUAL(step * 10, lv_spinbox_get_step(spinbox_events)); + + /* Spinbox should divide it's step vale by 10 after receiving the LV_KEY_RIGHT event */ + step = lv_spinbox_get_step(spinbox_events); + key = LV_KEY_RIGHT; + lv_event_send(spinbox_events, LV_EVENT_KEY, (void *) &key); + + TEST_ASSERT_EQUAL(step / 10, lv_spinbox_get_step(spinbox_events)); +} + +void test_spinbox_event_key_encoder_indev_turn_right(void) +{ + /* Setup group and encoder indev */ + lv_group_add_obj(g, spinbox_events); + + /* Spinbox should increment it's value by one step after receiving the LV_KEY_UP event */ + lv_spinbox_set_value(spinbox_events, 0); + + lv_test_encoder_click(); + lv_test_encoder_turn(1); + + TEST_ASSERT_EQUAL(1, lv_spinbox_get_value(spinbox_events)); +} + +void test_spinbox_event_key_encoder_indev_turn_left(void) +{ + int32_t value = 10; + /* Setup group and encoder indev */ + lv_group_add_obj(g, spinbox_events); + + /* Spinbox should decrement it's value by one step after receiving the LV_KEY_UP event */ + lv_spinbox_set_value(spinbox_events, value); + lv_spinbox_set_cursor_pos(spinbox_events, 0); + + lv_test_encoder_click(); + lv_test_encoder_turn(-1); + TEST_ASSERT_EQUAL(value - 1, lv_spinbox_get_value(spinbox_events)); +} + +void test_spinbox_event_key_encoder_indev_editing_group(void) +{ + int32_t value = 10; + /* Setup group and encoder indev */ + lv_spinbox_set_range(spinbox_events, 0, 20); + lv_group_add_obj(g, spinbox_events); + lv_group_set_editing(g, true); + + lv_spinbox_set_value(spinbox_events, value); + lv_spinbox_set_cursor_pos(spinbox_events, 0); + + lv_test_encoder_click(); + lv_test_encoder_turn(-1); + TEST_ASSERT_EQUAL(0, lv_spinbox_get_value(spinbox_events)); + /* digit_count is 5, so we expect to be in the position of the MSB digit */ + TEST_ASSERT_EQUAL(1000, lv_spinbox_get_step(spinbox_events)); + + /* Test with digit_count == 1 */ + lv_spinbox_set_digit_format(spinbox_events, 1, 2); + lv_spinbox_set_cursor_pos(spinbox_events, 0); + + lv_test_encoder_click(); + lv_test_encoder_turn(-1); + TEST_ASSERT_EQUAL(0, lv_spinbox_get_value(spinbox_events)); + /* digit_count is 1, so we expect to be in the same position */ + TEST_ASSERT_EQUAL(1, lv_spinbox_get_step(spinbox_events)); +} + +void test_spinbox_event_key_encoder_indev_editing_group_left_step_direction(void) +{ + int32_t value = 10; + /* Setup group and encoder indev */ + lv_spinbox_set_digit_step_direction(spinbox_events, LV_DIR_LEFT); + lv_spinbox_set_range(spinbox_events, 0, 20); + lv_group_add_obj(g, spinbox_events); + lv_group_set_editing(g, true); + + lv_spinbox_set_value(spinbox_events, value); + lv_spinbox_set_cursor_pos(spinbox_events, 0); + + lv_test_encoder_click(); + lv_test_encoder_turn(-1); + TEST_ASSERT_EQUAL(0, lv_spinbox_get_value(spinbox_events)); + /* digit_count is 5, we expect to be in the position next to the left */ + TEST_ASSERT_EQUAL(10, lv_spinbox_get_step(spinbox_events)); + + /* Test with digit_count == 1 */ + lv_spinbox_set_digit_format(spinbox_events, 2, 2); + lv_spinbox_set_cursor_pos(spinbox_events, 1); + + lv_test_encoder_click(); + lv_test_encoder_turn(-1); + TEST_ASSERT_EQUAL(0, lv_spinbox_get_value(spinbox_events)); + /* digit_count is 1, so we expect to be in the same position */ + TEST_ASSERT_EQUAL(1, lv_spinbox_get_step(spinbox_events)); +} + +void test_spinbox_event_release(void) +{ + lv_spinbox_set_value(spinbox_events, 0); + lv_spinbox_set_digit_format(spinbox_events, 5, 2); + + /* Set cursor in least significant decimal digit */ + lv_spinbox_set_cursor_pos(spinbox_events, 0); + lv_event_send(spinbox_events, LV_EVENT_RELEASED, NULL); + + TEST_ASSERT_EQUAL(1, lv_spinbox_get_step(spinbox_events)); +} + +void test_spinbox_zero_crossing(void) +{ + int32_t value = -13; + /* Setup group and encoder indev */ + lv_spinbox_set_digit_step_direction(spinbox_events, LV_DIR_LEFT); + lv_spinbox_set_range(spinbox_events, -20, 20); + lv_group_add_obj(g, spinbox_events); + + lv_spinbox_set_value(spinbox_events, value); + lv_spinbox_set_cursor_pos(spinbox_events, 1); + + lv_test_encoder_click(); + lv_test_encoder_turn(1); + TEST_ASSERT_EQUAL(-3, lv_spinbox_get_value(spinbox_events)); + + lv_test_encoder_turn(1); + TEST_ASSERT_EQUAL(3, lv_spinbox_get_value(spinbox_events)); + + lv_test_encoder_turn(1); + TEST_ASSERT_EQUAL(13, lv_spinbox_get_value(spinbox_events)); +} + +#endif