From a1a909fc24e76fba738225e037b9b24007debdb6 Mon Sep 17 00:00:00 2001 From: Liam <30486941+liamHowatt@users.noreply.github.com> Date: Thu, 2 May 2024 09:54:25 -0400 Subject: [PATCH] feat(gridnav): single axis movement flags (#6044) --- docs/others/gridnav.rst | 9 +- examples/others/gridnav/index.rst | 8 +- examples/others/gridnav/lv_example_gridnav.h | 1 + .../others/gridnav/lv_example_gridnav_5.c | 69 +++++++++++ src/others/gridnav/lv_gridnav.c | 8 +- src/others/gridnav/lv_gridnav.h | 12 ++ tests/src/lv_test_conf_full.h | 1 + tests/src/test_cases/test_gridnav.c | 114 ++++++++++++++++++ 8 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 examples/others/gridnav/lv_example_gridnav_5.c create mode 100644 tests/src/test_cases/test_gridnav.c diff --git a/docs/others/gridnav.rst b/docs/others/gridnav.rst index 06c1c4568..74e2722c1 100644 --- a/docs/others/gridnav.rst +++ b/docs/others/gridnav.rst @@ -27,7 +27,7 @@ the object so that gridnav can process the arrow keys. To move the focus to the next widget of the group use :cpp:enumerator:`LV_KEY_NEXT` or :cpp:enumerator:`LV_KEY_PREV`. -Optionally you can also use :cpp:func:`lv_group_focus_next` +Optionally you can also use :cpp:func:`lv_group_focus_next` or :cpp:func:`lv_group_focus_prev` or the ``TAB`` key on keyboard as usual. @@ -52,6 +52,13 @@ To add the gridnav feature to an object use object can be scrolled in that direction then it will be scrolled instead of going to the next/previous object. If there is no more room for scrolling the next/previous object will be focused normally +- :cpp:enumerator:`LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY`: Only use the left/right keys + for grid navigation. Up/down key events will be sent to the focused object. +- :cpp:enumerator:`LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY`: Only use the up/down keys + for grid navigation. Left/right key events will be sent to the focused object. + +:cpp:enumerator:`LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY` and :cpp:enumerator:`LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY` +should not be used together. :cpp:expr:`lv_gridnav_remove(cont)` Removes gridnav from an object. diff --git a/examples/others/gridnav/index.rst b/examples/others/gridnav/index.rst index d06978918..356df4516 100644 --- a/examples/others/gridnav/index.rst +++ b/examples/others/gridnav/index.rst @@ -21,4 +21,10 @@ Simple navigation on a list widget ---------------------------------- .. lv_example:: others/gridnav/lv_example_gridnav_4 - :language: c \ No newline at end of file + :language: c + +Grid navigation for only one axis +--------------------------------- + +.. lv_example:: others/gridnav/lv_example_gridnav_5 + :language: c diff --git a/examples/others/gridnav/lv_example_gridnav.h b/examples/others/gridnav/lv_example_gridnav.h index 1389c9067..1c344d0ac 100644 --- a/examples/others/gridnav/lv_example_gridnav.h +++ b/examples/others/gridnav/lv_example_gridnav.h @@ -29,6 +29,7 @@ void lv_example_gridnav_1(void); void lv_example_gridnav_2(void); void lv_example_gridnav_3(void); void lv_example_gridnav_4(void); +void lv_example_gridnav_5(void); /********************** * MACROS diff --git a/examples/others/gridnav/lv_example_gridnav_5.c b/examples/others/gridnav/lv_example_gridnav_5.c new file mode 100644 index 000000000..217daad41 --- /dev/null +++ b/examples/others/gridnav/lv_example_gridnav_5.c @@ -0,0 +1,69 @@ +#include "../../lv_examples.h" +#if LV_USE_GRIDNAV && LV_USE_FLEX && LV_BUILD_EXAMPLES + +static const char * opts[] = {"0\n1\n2\n3\n4\n5", "0\n1\n2\n3\n4\n5\n6\n7\n8\n9", "s\nm\nh"}; +static const int32_t opts_counts[] = {6, 10, 3}; + +static lv_obj_t * sliders[3]; +static lv_obj_t * rollers[3]; + +static void slider_key_cb(lv_event_t * e) +{ + uint8_t i = (uint32_t)(uintptr_t)lv_event_get_user_data(e); + lv_roller_set_selected(rollers[i], lv_slider_get_value(sliders[i]), LV_ANIM_ON); +} +static void roller_key_cb(lv_event_t * e) +{ + uint8_t i = (uint32_t)(uintptr_t)lv_event_get_user_data(e); + lv_slider_set_value(sliders[i], lv_roller_get_selected(rollers[i]), LV_ANIM_ON); +} + +/** + * Grid navigation for only one axis + */ +void lv_example_gridnav_5(void) +{ + /*It's assumed that the default group is set and + *there is a keyboard indev*/ + + lv_group_t * group = lv_group_get_default(); + lv_obj_t * cont; + + cont = lv_obj_create(lv_screen_active()); + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_size(cont, lv_pct(100), lv_pct(50)); + lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, 0); + /* only up/down keys will be used for grid navigation in this container. */ + /* left/right will be sent to the sliders */ + lv_gridnav_add(cont, LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY); + lv_group_add_obj(group, cont); + for(uint32_t i = 0; i < 3; i++) { + lv_obj_t * slider = lv_slider_create(cont); + lv_slider_set_range(slider, 0, opts_counts[i] - 1); + lv_group_remove_obj(slider); + lv_obj_set_width(slider, lv_pct(85)); + sliders[i] = slider; + lv_obj_add_event_cb(slider, slider_key_cb, LV_EVENT_KEY, (void *)(uintptr_t)i); + } + + cont = lv_obj_create(lv_screen_active()); + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_size(cont, lv_pct(100), lv_pct(50)); + lv_obj_align(cont, LV_ALIGN_BOTTOM_MID, 0, 0); + /* only left/right keys will be used for grid navigation in this container. */ + /* up/down will be sent to the rollers */ + lv_gridnav_add(cont, LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY); + lv_group_add_obj(group, cont); + for(uint32_t i = 0; i < 3; i++) { + lv_obj_t * roller = lv_roller_create(cont); + lv_roller_set_options(roller, opts[i], LV_ROLLER_MODE_INFINITE); + lv_obj_set_size(roller, lv_pct(30), lv_pct(100)); + lv_group_remove_obj(roller); + rollers[i] = roller; + lv_obj_add_event_cb(roller, roller_key_cb, LV_EVENT_KEY, (void *)(uintptr_t)i); + } +} + +#endif diff --git a/src/others/gridnav/lv_gridnav.c b/src/others/gridnav/lv_gridnav.c index f49c62d0c..e5e3183e5 100644 --- a/src/others/gridnav/lv_gridnav.c +++ b/src/others/gridnav/lv_gridnav.c @@ -143,7 +143,7 @@ static void gridnav_event_cb(lv_event_t * e) uint32_t key = lv_event_get_key(e); lv_obj_t * guess = NULL; - if(key == LV_KEY_RIGHT) { + if(key == LV_KEY_RIGHT && !(dsc->ctrl & LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY)) { if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) && lv_obj_get_scroll_right(dsc->focused_obj) > 0) { int32_t d = lv_obj_get_width(dsc->focused_obj) / 4; @@ -163,7 +163,7 @@ static void gridnav_event_cb(lv_event_t * e) } } } - else if(key == LV_KEY_LEFT) { + else if(key == LV_KEY_LEFT && !(dsc->ctrl & LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY)) { if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) && lv_obj_get_scroll_left(dsc->focused_obj) > 0) { int32_t d = lv_obj_get_width(dsc->focused_obj) / 4; @@ -183,7 +183,7 @@ static void gridnav_event_cb(lv_event_t * e) } } } - else if(key == LV_KEY_DOWN) { + else if(key == LV_KEY_DOWN && !(dsc->ctrl & LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY)) { if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) && lv_obj_get_scroll_bottom(dsc->focused_obj) > 0) { int32_t d = lv_obj_get_height(dsc->focused_obj) / 4; @@ -202,7 +202,7 @@ static void gridnav_event_cb(lv_event_t * e) } } } - else if(key == LV_KEY_UP) { + else if(key == LV_KEY_UP && !(dsc->ctrl & LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY)) { if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) && lv_obj_get_scroll_top(dsc->focused_obj) > 0) { int32_t d = lv_obj_get_height(dsc->focused_obj) / 4; diff --git a/src/others/gridnav/lv_gridnav.h b/src/others/gridnav/lv_gridnav.h index 29b86fc1d..50fff3270 100644 --- a/src/others/gridnav/lv_gridnav.h +++ b/src/others/gridnav/lv_gridnav.h @@ -80,6 +80,18 @@ typedef enum { * If there is no more room for scrolling the next/previous object will be focused normally */ LV_GRIDNAV_CTRL_SCROLL_FIRST = 0x2, + /** + * Only use left/right keys for grid navigation. Up/down key events will be sent to the + * focused object. + */ + LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY = 0x4, + + /** + * Only use up/down keys for grid navigation. Left/right key events will be sent to the + * focused object. + */ + LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY = 0x8 + } lv_gridnav_ctrl_t; /********************** diff --git a/tests/src/lv_test_conf_full.h b/tests/src/lv_test_conf_full.h index 198c35ade..1988c6000 100644 --- a/tests/src/lv_test_conf_full.h +++ b/tests/src/lv_test_conf_full.h @@ -90,6 +90,7 @@ #define LV_USE_VECTOR_GRAPHIC 1 #define LV_USE_PROFILER 1 #define LV_PROFILER_INCLUDE "lv_profiler_builtin.h" +#define LV_USE_GRIDNAV 1 #define LV_BUILD_EXAMPLES 1 #define LV_USE_DEMO_WIDGETS 1 diff --git a/tests/src/test_cases/test_gridnav.c b/tests/src/test_cases/test_gridnav.c new file mode 100644 index 000000000..014a74772 --- /dev/null +++ b/tests/src/test_cases/test_gridnav.c @@ -0,0 +1,114 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" + +#include "unity/unity.h" +#include "lv_test_indev.h" + +static lv_obj_t * g_screen; +static lv_group_t * g_group; +static struct { + bool press_happened; + uint32_t key; + lv_obj_t * obj; +} g_key_data; + +void setUp(void) +{ + g_screen = lv_screen_active(); + g_group = lv_group_create(); + g_key_data.press_happened = false; +} + +void tearDown(void) +{ + lv_obj_clean(g_screen); + lv_group_delete(g_group); /* also removes all indevs set to the group */ +} + +static void key_event_cb(lv_event_t * e) +{ + TEST_ASSERT_FALSE(g_key_data.press_happened); + g_key_data.press_happened = true; + g_key_data.key = *(uint32_t *)lv_event_get_param(e); + g_key_data.obj = lv_event_get_target_obj(e); +} + +static void gridnav_one_axis_move_only(uint32_t key_grid_axis_next, + uint32_t key_grid_axis_prev, + uint32_t key_obj_axis_next, + uint32_t key_obj_axis_prev, + lv_gridnav_ctrl_t gridnav_ctrl, + lv_flex_flow_t flex_flow) +{ + lv_indev_set_group(lv_test_keypad_indev, g_group); + + lv_obj_t * cont = lv_obj_create(g_screen); + lv_obj_set_flex_flow(cont, flex_flow); + lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_size(cont, lv_pct(100), lv_pct(100)); + lv_obj_center(cont); + lv_gridnav_add(cont, gridnav_ctrl); + lv_group_add_obj(g_group, cont); + lv_obj_t * objs[3]; + for(uint32_t i = 0; i < 3; i++) { + lv_obj_t * obj = lv_obj_create(cont); + lv_obj_create(obj); /* the obj needs a child to be focusable by gridnav */ + lv_group_remove_obj(obj); + lv_obj_add_event_cb(obj, key_event_cb, LV_EVENT_KEY, NULL); + objs[i] = obj; + } + + TEST_ASSERT(lv_obj_get_state(objs[0]) & LV_STATE_FOCUSED); + + /* gridnav direction key moves the focus */ + lv_test_key_hit(key_grid_axis_next); + TEST_ASSERT(lv_obj_get_state(objs[1]) & LV_STATE_FOCUSED); + TEST_ASSERT_FALSE(g_key_data.press_happened); + + /* non gridnav direction key does not move the focus. */ + /* the key is sent to the object instead */ + lv_test_key_hit(key_obj_axis_next); + TEST_ASSERT(lv_obj_get_state(objs[1]) & LV_STATE_FOCUSED); + TEST_ASSERT_TRUE(g_key_data.press_happened); + TEST_ASSERT(g_key_data.key == key_obj_axis_next); + TEST_ASSERT(g_key_data.obj == objs[1]); + g_key_data.press_happened = false; + + lv_test_key_hit(key_obj_axis_prev); + TEST_ASSERT(lv_obj_get_state(objs[1]) & LV_STATE_FOCUSED); + TEST_ASSERT_TRUE(g_key_data.press_happened); + TEST_ASSERT(g_key_data.key == key_obj_axis_prev); + TEST_ASSERT(g_key_data.obj == objs[1]); + g_key_data.press_happened = false; + + /* go back */ + lv_test_key_hit(key_grid_axis_prev); + TEST_ASSERT(lv_obj_get_state(objs[0]) & LV_STATE_FOCUSED); + TEST_ASSERT_FALSE(g_key_data.press_happened); + /* at the beginning, can't move further back */ + lv_test_key_hit(key_grid_axis_prev); + TEST_ASSERT(lv_obj_get_state(objs[0]) & LV_STATE_FOCUSED); + TEST_ASSERT_FALSE(g_key_data.press_happened); +} + +void test_gridnav_vertical_move_only(void) +{ + gridnav_one_axis_move_only(LV_KEY_DOWN, + LV_KEY_UP, + LV_KEY_RIGHT, + LV_KEY_LEFT, + LV_GRIDNAV_CTRL_VERTICAL_MOVE_ONLY, + LV_FLEX_FLOW_COLUMN); +} + +void test_gridnav_horizontal_move_only(void) +{ + gridnav_one_axis_move_only(LV_KEY_RIGHT, + LV_KEY_LEFT, + LV_KEY_DOWN, + LV_KEY_UP, + LV_GRIDNAV_CTRL_HORIZONTAL_MOVE_ONLY, + LV_FLEX_FLOW_ROW); +} + +#endif