1
0
mirror of https://github.com/lvgl/lvgl.git synced 2025-02-04 07:13:00 +08:00

feat(gridnav): single axis movement flags (#6044)

This commit is contained in:
Liam 2024-05-02 09:54:25 -04:00 committed by GitHub
parent 94651d8620
commit a1a909fc24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 216 additions and 6 deletions

View File

@ -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 To move the focus to the next widget of the group use
:cpp:enumerator:`LV_KEY_NEXT` or :cpp:enumerator:`LV_KEY_PREV`. :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`` or :cpp:func:`lv_group_focus_prev` or the ``TAB``
key on keyboard as usual. 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 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 going to the next/previous object. If there is no more room for scrolling the
next/previous object will be focused normally 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. :cpp:expr:`lv_gridnav_remove(cont)` Removes gridnav from an object.

View File

@ -21,4 +21,10 @@ Simple navigation on a list widget
---------------------------------- ----------------------------------
.. lv_example:: others/gridnav/lv_example_gridnav_4 .. lv_example:: others/gridnav/lv_example_gridnav_4
:language: c :language: c
Grid navigation for only one axis
---------------------------------
.. lv_example:: others/gridnav/lv_example_gridnav_5
:language: c

View File

@ -29,6 +29,7 @@ void lv_example_gridnav_1(void);
void lv_example_gridnav_2(void); void lv_example_gridnav_2(void);
void lv_example_gridnav_3(void); void lv_example_gridnav_3(void);
void lv_example_gridnav_4(void); void lv_example_gridnav_4(void);
void lv_example_gridnav_5(void);
/********************** /**********************
* MACROS * MACROS

View File

@ -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

View File

@ -143,7 +143,7 @@ static void gridnav_event_cb(lv_event_t * e)
uint32_t key = lv_event_get_key(e); uint32_t key = lv_event_get_key(e);
lv_obj_t * guess = NULL; 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) && 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) { lv_obj_get_scroll_right(dsc->focused_obj) > 0) {
int32_t d = lv_obj_get_width(dsc->focused_obj) / 4; 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) && 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) { lv_obj_get_scroll_left(dsc->focused_obj) > 0) {
int32_t d = lv_obj_get_width(dsc->focused_obj) / 4; 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) && 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) { lv_obj_get_scroll_bottom(dsc->focused_obj) > 0) {
int32_t d = lv_obj_get_height(dsc->focused_obj) / 4; 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) && 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) { lv_obj_get_scroll_top(dsc->focused_obj) > 0) {
int32_t d = lv_obj_get_height(dsc->focused_obj) / 4; int32_t d = lv_obj_get_height(dsc->focused_obj) / 4;

View File

@ -80,6 +80,18 @@ typedef enum {
* If there is no more room for scrolling the next/previous object will be focused normally */ * If there is no more room for scrolling the next/previous object will be focused normally */
LV_GRIDNAV_CTRL_SCROLL_FIRST = 0x2, 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; } lv_gridnav_ctrl_t;
/********************** /**********************

View File

@ -90,6 +90,7 @@
#define LV_USE_VECTOR_GRAPHIC 1 #define LV_USE_VECTOR_GRAPHIC 1
#define LV_USE_PROFILER 1 #define LV_USE_PROFILER 1
#define LV_PROFILER_INCLUDE "lv_profiler_builtin.h" #define LV_PROFILER_INCLUDE "lv_profiler_builtin.h"
#define LV_USE_GRIDNAV 1
#define LV_BUILD_EXAMPLES 1 #define LV_BUILD_EXAMPLES 1
#define LV_USE_DEMO_WIDGETS 1 #define LV_USE_DEMO_WIDGETS 1

View File

@ -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