From 62fc7123f1f5da9d81b82445f5e18e8a1ba4f4c1 Mon Sep 17 00:00:00 2001 From: Gabor Kiss-Vamosi Date: Thu, 20 Jan 2022 10:28:49 +0100 Subject: [PATCH] feat(gridnav): add lv_gridnav (#2911) * add first implememtation * Update src/extra/others/gridnav/lv_gridnav.c Co-authored-by: embeddedt <42941056+embeddedt@users.noreply.github.com> * minor fix * add example and minor fixes * add more examples * add more examples * code formatting * add LV_GRIDNAC_CTRL_SCROLL_FIRST * code formatting * add example for list * add docs * Misc: improvements to gridnav docs (#2994) Co-authored-by: embeddedt <42941056+embeddedt@users.noreply.github.com> Co-authored-by: Ken Carpenter <62639971+FoundationKen@users.noreply.github.com> --- docs/others/gridnav.md | 60 +++ docs/others/index.md | 1 + examples/layouts/flex/lv_example_flex_2.c | 1 + examples/others/gridnav/index.rst | 18 + examples/others/gridnav/lv_example_gridnav.h | 40 ++ .../others/gridnav/lv_example_gridnav_1.c | 72 ++++ .../others/gridnav/lv_example_gridnav_2.c | 44 +++ .../others/gridnav/lv_example_gridnav_3.c | 101 +++++ .../others/gridnav/lv_example_gridnav_4.c | 47 +++ examples/others/lv_example_others.h | 1 + examples/widgets/list/lv_example_list_1.c | 1 + lv_conf_template.h | 7 +- src/core/lv_obj_scroll.c | 99 +++-- src/core/lv_obj_scroll.h | 18 +- src/extra/others/gridnav/lv_gridnav.c | 354 ++++++++++++++++++ src/extra/others/gridnav/lv_gridnav.h | 115 ++++++ src/extra/others/lv_others.h | 1 + src/lv_conf_internal.h | 21 +- 18 files changed, 948 insertions(+), 53 deletions(-) create mode 100644 docs/others/gridnav.md create mode 100644 examples/others/gridnav/index.rst create mode 100644 examples/others/gridnav/lv_example_gridnav.h create mode 100644 examples/others/gridnav/lv_example_gridnav_1.c create mode 100644 examples/others/gridnav/lv_example_gridnav_2.c create mode 100644 examples/others/gridnav/lv_example_gridnav_3.c create mode 100644 examples/others/gridnav/lv_example_gridnav_4.c create mode 100644 src/extra/others/gridnav/lv_gridnav.c create mode 100644 src/extra/others/gridnav/lv_gridnav.h diff --git a/docs/others/gridnav.md b/docs/others/gridnav.md new file mode 100644 index 000000000..3d9acb6b6 --- /dev/null +++ b/docs/others/gridnav.md @@ -0,0 +1,60 @@ +```eval_rst +.. include:: /header.rst +:github_url: |github_link_base|/others/gridnav.md +``` +# Grid navigation + +Grid navigation (gridnav for short) is a feature that changes the currently focused child object as arrow keys are pressed. + +If the children are arranged into a grid-like layout then the up, down, left and right arrows move focus to the nearest sibling +in the respective direction. + +It doesn't matter how the children are positioned, as only the current x and y coordinates are considered. +This means that gridnav works with manually positioned children, as well as [Flex](/layouts/flex.html) and [Grid](/layouts/grid.html) layouts. + +Gridnav also works if the children are arranged into a single row or column. +That makes it useful, for example, to simplify navigation on a [List widget](/widgets/extra/list.html). + +Gridnav assumes that the object to which gridnav is added is part of a [group](/overview/indev.html#groups). +This way, if the object with gridnav is focused, the arrow key presses are automatically forwarded to the object +so that gridnav can process the arrow keys. + +To move the focus to the next widget of the group use `LV_KEY_NEXT/PREV` or `lv_group_focus_next/prev()` or the `TAB` key on keyboard as usual. + +If the container is scrollable and the focused child is out of the view, gridnav will automatically scroll the child into view. + +## Usage + +To add the gridnav feature to an object use `lv_gridnav_add(cont, flags)`. + +`flags` control the behavior of gridnav: +- `LV_GRIDNAV_CTRL_NONE` Default settings +- `LV_GRIDNAV_CTRL_ROLLOVER` If there is no next/previous object in a direction, +the focus goes to the object in the next/previous row (on left/right keys) or first/last row (on up/down keys +- `LV_GRIDNAV_CTRL_SCROLL_FIRST` If an arrow is pressed and the focused 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 + +`lv_gridnav_remove(cont)` Removes gridnav from an object. + +## Focusable objects + +An object needs to be clickable or click focusable (`LV_OBJ_FLAG_CLICKABLE` or `LV_OBJ_FLAG_CLICK_FOCUSABLE`) +and not hidden (`LV_OBJ_FLAG_HIDDEN`) to be focusable by gridnav. + + +## Example + +```eval_rst + +.. include:: ../../examples/others/gridnav/index.rst + +``` +## API + + +```eval_rst + +.. doxygenfile:: lv_gridnav.h + :project: lvgl + +``` diff --git a/docs/others/index.md b/docs/others/index.md index 5dbd6f9b2..977363fcb 100644 --- a/docs/others/index.md +++ b/docs/others/index.md @@ -12,5 +12,6 @@ snapshot monkey + gridnav ``` diff --git a/examples/layouts/flex/lv_example_flex_2.c b/examples/layouts/flex/lv_example_flex_2.c index 9870a0e7f..dc60744b0 100644 --- a/examples/layouts/flex/lv_example_flex_2.c +++ b/examples/layouts/flex/lv_example_flex_2.c @@ -21,6 +21,7 @@ void lv_example_flex_2(void) for(i = 0; i < 8; i++) { lv_obj_t * obj = lv_obj_create(cont); lv_obj_set_size(obj, 70, LV_SIZE_CONTENT); + lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE); lv_obj_t * label = lv_label_create(obj); lv_label_set_text_fmt(label, "%"LV_PRIu32, i); diff --git a/examples/others/gridnav/index.rst b/examples/others/gridnav/index.rst new file mode 100644 index 000000000..6f6909881 --- /dev/null +++ b/examples/others/gridnav/index.rst @@ -0,0 +1,18 @@ + +Basic grid navigation +""""""""""""""""""""" + +.. lv_example:: others/monkey/lv_example_gridnav_1 + :language: c + +Grid navigation on a list +"""""""""""""""""""""""" + +.. lv_example:: others/monkey/lv_example_gridnav_2 + :language: c + +Nested grid navigations +""""""""""""""""""""""" + +.. lv_example:: others/monkey/lv_example_gridanav_3 + :language: c diff --git a/examples/others/gridnav/lv_example_gridnav.h b/examples/others/gridnav/lv_example_gridnav.h new file mode 100644 index 000000000..efe5e2693 --- /dev/null +++ b/examples/others/gridnav/lv_example_gridnav.h @@ -0,0 +1,40 @@ +/** + * @file lv_example_gridnav.h + * + */ + +#ifndef LV_EXAMPLE_GRIDNAV_H +#define LV_EXAMPLE_GRIDNAV_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_example_gridnav_1(void); +void lv_example_gridnav_2(void); +void lv_example_gridnav_3(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_EXAMPLE_GRIDNAV_H*/ diff --git a/examples/others/gridnav/lv_example_gridnav_1.c b/examples/others/gridnav/lv_example_gridnav_1.c new file mode 100644 index 000000000..64465a10c --- /dev/null +++ b/examples/others/gridnav/lv_example_gridnav_1.c @@ -0,0 +1,72 @@ +#include "../../lv_examples.h" +#if LV_USE_GRIDNAV && LV_USE_FLEX && LV_BUILD_EXAMPLES + +/** + * Demonstrate a a basic grid navigation + */ +void lv_example_gridnav_1(void) +{ + /*It's assumed that the default group is set and + *there is a keyboard indev*/ + + lv_obj_t * cont1 = lv_obj_create(lv_scr_act()); + lv_gridnav_add(cont1, LV_GRIDNAV_CTRL_NONE); + + /*Use flex here, but works with grid or manually placed objects as well*/ + lv_obj_set_flex_flow(cont1, LV_FLEX_FLOW_ROW_WRAP); + lv_obj_set_style_bg_color(cont1, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED); + lv_obj_set_size(cont1, lv_pct(50), lv_pct(100)); + + /*Only the container needs to be in a group*/ + lv_group_add_obj(lv_group_get_default(), cont1); + + lv_obj_t * label = lv_label_create(cont1); + lv_label_set_text_fmt(label, "No rollover"); + + uint32_t i; + for(i = 0; i < 10; i++) { + lv_obj_t * obj = lv_btn_create(cont1); + lv_obj_set_size(obj, 70, LV_SIZE_CONTENT); + lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE); + lv_group_remove_obj(obj); /*Not needed, we use the gridnav instead*/ + + lv_obj_t * label = lv_label_create(obj); + lv_label_set_text_fmt(label, "%d", i); + lv_obj_center(label); + } + + /* Create a second container with rollover grid nav mode.*/ + + lv_obj_t * cont2 = lv_obj_create(lv_scr_act()); + lv_gridnav_add(cont2, LV_GRIDNAV_CTRL_ROLLOVER); + lv_obj_set_style_bg_color(cont2, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED); + lv_obj_set_size(cont2, lv_pct(50), lv_pct(100)); + lv_obj_align(cont2, LV_ALIGN_RIGHT_MID, 0, 0); + + label = lv_label_create(cont2); + lv_obj_set_width(label, lv_pct(100)); + lv_label_set_text_fmt(label, "Rollover\nUse tab to focus the other container"); + + /*Only the container needs to be in a group*/ + lv_group_add_obj(lv_group_get_default(), cont2); + + /*Add and place some children manually*/ + lv_obj_t * ta = lv_textarea_create(cont2); + lv_obj_set_size(ta, lv_pct(100), 80); + lv_obj_set_pos(ta, 0, 80); + lv_group_remove_obj(ta); /*Not needed, we use the gridnav instead*/ + + lv_obj_t * cb = lv_checkbox_create(cont2); + lv_obj_set_pos(cb, 0, 170); + lv_group_remove_obj(cb); /*Not needed, we use the gridnav instead*/ + + lv_obj_t * sw1 = lv_switch_create(cont2); + lv_obj_set_pos(sw1, 0, 200); + lv_group_remove_obj(sw1); /*Not needed, we use the gridnav instead*/ + + lv_obj_t * sw2 = lv_switch_create(cont2); + lv_obj_set_pos(sw2, lv_pct(50), 200); + lv_group_remove_obj(sw2); /*Not needed, we use the gridnav instead*/ +} + +#endif diff --git a/examples/others/gridnav/lv_example_gridnav_2.c b/examples/others/gridnav/lv_example_gridnav_2.c new file mode 100644 index 000000000..b757187a5 --- /dev/null +++ b/examples/others/gridnav/lv_example_gridnav_2.c @@ -0,0 +1,44 @@ +#include "../../lv_examples.h" +#if LV_USE_GRIDNAV && LV_USE_LIST && LV_BUILD_EXAMPLES + +/** + * Grid navigation on a list + */ +void lv_example_gridnav_2(void) +{ + /*It's assumed that the default group is set and + *there is a keyboard indev*/ + + lv_obj_t * list1 = lv_list_create(lv_scr_act()); + lv_gridnav_add(list1, LV_GRIDNAV_CTRL_NONE); + lv_obj_set_size(list1, lv_pct(45), lv_pct(80)); + lv_obj_align(list1, LV_ALIGN_LEFT_MID, 5, 0); + lv_obj_set_style_bg_color(list1, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED); + lv_group_add_obj(lv_group_get_default(), list1); + + + char buf[32]; + uint32_t i; + for(i = 0; i < 15; i++) { + lv_snprintf(buf, sizeof(buf), "File %d", i + 1); + lv_obj_t * item = lv_list_add_btn(list1, LV_SYMBOL_FILE, buf); + lv_obj_set_style_bg_opa(item, 0, 0); + lv_group_remove_obj(item); /*Not needed, we use the gridnav instead*/ + } + + lv_obj_t * list2 = lv_list_create(lv_scr_act()); + lv_gridnav_add(list2, LV_GRIDNAV_CTRL_ROLLOVER); + lv_obj_set_size(list2, lv_pct(45), lv_pct(80)); + lv_obj_align(list2, LV_ALIGN_RIGHT_MID, -5, 0); + lv_obj_set_style_bg_color(list2, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED); + lv_group_add_obj(lv_group_get_default(), list2); + + for(i = 0; i < 15; i++) { + lv_snprintf(buf, sizeof(buf), "Folder %d", i + 1); + lv_obj_t * item = lv_list_add_btn(list2, LV_SYMBOL_DIRECTORY, buf); + lv_obj_set_style_bg_opa(item, 0, 0); + lv_group_remove_obj(item); + } +} + +#endif diff --git a/examples/others/gridnav/lv_example_gridnav_3.c b/examples/others/gridnav/lv_example_gridnav_3.c new file mode 100644 index 000000000..b31f7f757 --- /dev/null +++ b/examples/others/gridnav/lv_example_gridnav_3.c @@ -0,0 +1,101 @@ +#include "../../lv_examples.h" +#if LV_USE_GRIDNAV && LV_USE_FLEX && LV_BUILD_EXAMPLES + +static void cont_sub_event_cb(lv_event_t * e) +{ + uint32_t k = lv_event_get_key(e); + lv_obj_t * obj = lv_event_get_current_target(e); + if(k == LV_KEY_ENTER) { + lv_group_focus_obj(obj); + } + else if(k == LV_KEY_ESC) { + lv_group_focus_next(lv_obj_get_group(obj)); + } + +} + +/** + * Nested grid navigations + */ +void lv_example_gridnav_3(void) +{ + /*It's assumed that the default group is set and + *there is a keyboard indev*/ + + lv_obj_t * cont_main = lv_obj_create(lv_scr_act()); + lv_gridnav_add(cont_main, LV_GRIDNAV_CTRL_ROLLOVER | LV_GRIDNAV_CTRL_SCROLL_FIRST); + + /*Only the container needs to be in a group*/ + lv_group_add_obj(lv_group_get_default(), cont_main); + + /*Use flex here, but works with grid or manually placed objects as well*/ + lv_obj_set_flex_flow(cont_main, LV_FLEX_FLOW_ROW_WRAP); + lv_obj_set_style_bg_color(cont_main, lv_palette_lighten(LV_PALETTE_BLUE, 5), LV_STATE_FOCUSED); + lv_obj_set_size(cont_main, lv_pct(80), LV_SIZE_CONTENT); + + lv_obj_t * btn; + lv_obj_t * label; + + btn = lv_btn_create(cont_main); + lv_group_remove_obj(btn); + label = lv_label_create(btn); + lv_label_set_text(label, "Button 1"); + + btn = lv_btn_create(cont_main); + lv_group_remove_obj(btn); + label = lv_label_create(btn); + lv_label_set_text(label, "Button 2"); + + + /*Create an other container with long text to show how LV_GRIDNAV_CTRL_SCROLL_FIRST works*/ + lv_obj_t * cont_sub1 = lv_obj_create(cont_main); + lv_obj_set_size(cont_sub1, lv_pct(100), 100); + + label = lv_label_create(cont_sub1); + lv_obj_set_style_bg_color(cont_sub1, lv_palette_lighten(LV_PALETTE_RED, 5), LV_STATE_FOCUSED); + lv_obj_set_width(label, lv_pct(100)); + lv_label_set_text(label, + "I'm a very long text which is makes my container scrollable. " + "As LV_GRIDNAV_FLAG_SCROLL_FIRST is enabled arrow will scroll me first " + "and a new objects will be focused only when an edge is reached with the scrolling.\n\n" + "This is only some placeholder text to be sure the parent will be scrollable. \n\n" + "Hello world!\n" + "Hello world!\n" + "Hello world!\n" + "Hello world!\n" + "Hello world!\n" + "Hello world!"); + + /*Create a third container that can be focused with ENTER and contains an other grid nav*/ + lv_obj_t * cont_sub2 = lv_obj_create(cont_main); + lv_gridnav_add(cont_sub2, LV_GRIDNAV_CTRL_ROLLOVER); + /*Only the container needs to be in a group*/ + lv_group_add_obj(lv_group_get_default(), cont_sub2); + + lv_obj_add_event_cb(cont_sub2, cont_sub_event_cb, LV_EVENT_KEY, NULL); + + /*Use flex here, but works with grid or manually placed objects as well*/ + lv_obj_set_flex_flow(cont_sub2, LV_FLEX_FLOW_ROW_WRAP); + lv_obj_set_style_bg_color(cont_sub2, lv_palette_lighten(LV_PALETTE_RED, 5), LV_STATE_FOCUSED); + lv_obj_set_size(cont_sub2, lv_pct(100), LV_SIZE_CONTENT); + + label = lv_label_create(cont_sub2); + lv_label_set_text(label, "Use ENTER/ESC to focus/defocus this container"); + lv_obj_set_width(label, lv_pct(100)); + + btn = lv_btn_create(cont_sub2); + lv_group_remove_obj(btn); + label = lv_label_create(btn); + lv_label_set_text(label, "Button 3"); + + btn = lv_btn_create(cont_sub2); + lv_group_remove_obj(btn); + label = lv_label_create(btn); + lv_label_set_text(label, "Button 4"); + + + + +} + +#endif diff --git a/examples/others/gridnav/lv_example_gridnav_4.c b/examples/others/gridnav/lv_example_gridnav_4.c new file mode 100644 index 000000000..42f1314d3 --- /dev/null +++ b/examples/others/gridnav/lv_example_gridnav_4.c @@ -0,0 +1,47 @@ +#include "../../lv_examples.h" +#if LV_USE_GRIDNAV && LV_USE_FLEX && LV_BUILD_EXAMPLES + + +static void event_handler(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_obj_t * list = lv_obj_get_parent(obj); + LV_LOG_USER("Clicked: %s", lv_list_get_btn_text(list, obj)); +} + +/** + * Simple navigation on a list widget + */ +void lv_example_gridnav_4(void) +{ + /*It's assumed that the default group is set and + *there is a keyboard indev*/ + + lv_obj_t * list = lv_list_create(lv_scr_act()); + lv_gridnav_add(list, LV_GRIDNAV_CTRL_ROLLOVER); + lv_obj_align(list, LV_ALIGN_LEFT_MID, 0, 10); + lv_group_add_obj(lv_group_get_default(), list); + + uint32_t i; + for(i = 0; i < 20; i++) { + char buf[32]; + + /*Add some separators too, they are not focusable by gridnav*/ + if((i % 5) == 0) { + lv_snprintf(buf, sizeof(buf), "Section %d", i / 5 + 1); + lv_list_add_text(list, buf); + } + + lv_snprintf(buf, sizeof(buf), "File %d", i + 1); + lv_obj_t * item = lv_list_add_btn(list, LV_SYMBOL_FILE, buf); + lv_obj_add_event_cb(item, event_handler, LV_EVENT_CLICKED, NULL); + lv_group_remove_obj(item); /*The default group adds it automatically*/ + } + + lv_obj_t * btn = lv_btn_create(lv_scr_act()); + lv_obj_align(btn, LV_ALIGN_RIGHT_MID, 0, -10); + lv_obj_t * label = lv_label_create(btn); + lv_label_set_text(label, "Button"); +} + +#endif diff --git a/examples/others/lv_example_others.h b/examples/others/lv_example_others.h index 808b4c6da..f054015fd 100644 --- a/examples/others/lv_example_others.h +++ b/examples/others/lv_example_others.h @@ -15,6 +15,7 @@ extern "C" { *********************/ #include "snapshot/lv_example_snapshot.h" #include "monkey/lv_example_monkey.h" +#include "gridnav/lv_example_gridnav.h" /********************* * DEFINES diff --git a/examples/widgets/list/lv_example_list_1.c b/examples/widgets/list/lv_example_list_1.c index f8467ad34..4e129f4c1 100644 --- a/examples/widgets/list/lv_example_list_1.c +++ b/examples/widgets/list/lv_example_list_1.c @@ -10,6 +10,7 @@ static void event_handler(lv_event_t * e) LV_LOG_USER("Clicked: %s", lv_list_get_btn_text(list1, obj)); } } + void lv_example_list_1(void) { /*Create a list*/ diff --git a/lv_conf_template.h b/lv_conf_template.h index ef3385c0d..00a518a69 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -621,10 +621,13 @@ *----------*/ /*1: Enable API to take snapshot for object*/ -#define LV_USE_SNAPSHOT 1 +#define LV_USE_SNAPSHOT 0 /*1: Enable Monkey test*/ -#define LV_USE_MONKEY 0 +#define LV_USE_MONKEY 0 + +/*1: Enable grid navigation*/ +#define LV_USE_GRIDNAV 0 /*================== * EXAMPLES diff --git a/src/core/lv_obj_scroll.c b/src/core/lv_obj_scroll.c index 0b2a5d26e..394a62060 100644 --- a/src/core/lv_obj_scroll.c +++ b/src/core/lv_obj_scroll.c @@ -252,9 +252,58 @@ void lv_obj_get_scroll_end(struct _lv_obj_t * obj, lv_point_t * end) * Other functions *====================*/ -void lv_obj_scroll_by(lv_obj_t * obj, lv_coord_t x, lv_coord_t y, lv_anim_enable_t anim_en) +void lv_obj_scroll_by_bounded(lv_obj_t * obj, lv_coord_t dx, lv_coord_t dy, lv_anim_enable_t anim_en) { - if(x == 0 && y == 0) return; + if(dx == 0 && dy == 0) return; + + /*We need to know the final sizes for bound check*/ + lv_obj_update_layout(obj); + + /*Don't let scroll more then naturally possible by the size of the content*/ + lv_coord_t x_current = -lv_obj_get_scroll_x(obj); + lv_coord_t x_bounded = x_current + dx; + + if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) != LV_BASE_DIR_RTL) { + if(x_bounded > 0) x_bounded = 0; + if(x_bounded < 0) { + lv_coord_t scroll_max = lv_obj_get_scroll_left(obj) + lv_obj_get_scroll_right(obj); + if(scroll_max < 0) scroll_max = 0; + + if(x_bounded < -scroll_max) x_bounded = -scroll_max; + } + } + else { + if(x_bounded < 0) x_bounded = 0; + if(x_bounded > 0) { + lv_coord_t scroll_max = lv_obj_get_scroll_left(obj) + lv_obj_get_scroll_right(obj); + if(scroll_max < 0) scroll_max = 0; + + if(x_bounded > scroll_max) x_bounded = scroll_max; + } + } + + /*Don't let scroll more then naturally possible by the size of the content*/ + lv_coord_t y_current = -lv_obj_get_scroll_y(obj); + lv_coord_t y_bounded = y_current + dy; + + if(y_bounded > 0) y_bounded = 0; + if(y_bounded < 0) { + lv_coord_t scroll_max = lv_obj_get_scroll_top(obj) + lv_obj_get_scroll_bottom(obj); + if(scroll_max < 0) scroll_max = 0; + if(y_bounded < -scroll_max) y_bounded = -scroll_max; + } + + dx = x_bounded - x_current; + dy = y_bounded - y_current; + if(dx || dy) { + lv_obj_scroll_by(obj, dx, dy, anim_en); + } +} + + +void lv_obj_scroll_by(lv_obj_t * obj, lv_coord_t dx, lv_coord_t dy, lv_anim_enable_t anim_en) +{ + if(dx == 0 && dy == 0) return; if(anim_en == LV_ANIM_ON) { lv_disp_t * d = lv_obj_get_disp(obj); lv_anim_t a; @@ -262,13 +311,13 @@ void lv_obj_scroll_by(lv_obj_t * obj, lv_coord_t x, lv_coord_t y, lv_anim_enable lv_anim_set_var(&a, obj); lv_anim_set_ready_cb(&a, scroll_anim_ready_cb); - if(x) { - uint32_t t = lv_anim_speed_to_time((lv_disp_get_hor_res(d) * 2) >> 2, 0, x); + if(dx) { + uint32_t t = lv_anim_speed_to_time((lv_disp_get_hor_res(d) * 2) >> 2, 0, dx); if(t < SCROLL_ANIM_TIME_MIN) t = SCROLL_ANIM_TIME_MIN; if(t > SCROLL_ANIM_TIME_MAX) t = SCROLL_ANIM_TIME_MAX; lv_anim_set_time(&a, t); lv_coord_t sx = lv_obj_get_scroll_x(obj); - lv_anim_set_values(&a, -sx, -sx + x); + lv_anim_set_values(&a, -sx, -sx + dx); lv_anim_set_exec_cb(&a, scroll_x_anim); lv_anim_set_path_cb(&a, lv_anim_path_ease_out); @@ -278,13 +327,13 @@ void lv_obj_scroll_by(lv_obj_t * obj, lv_coord_t x, lv_coord_t y, lv_anim_enable lv_anim_start(&a); } - if(y) { - uint32_t t = lv_anim_speed_to_time((lv_disp_get_ver_res(d) * 2) >> 2, 0, y); + if(dy) { + uint32_t t = lv_anim_speed_to_time((lv_disp_get_ver_res(d) * 2) >> 2, 0, dy); if(t < SCROLL_ANIM_TIME_MIN) t = SCROLL_ANIM_TIME_MIN; if(t > SCROLL_ANIM_TIME_MAX) t = SCROLL_ANIM_TIME_MAX; lv_anim_set_time(&a, t); lv_coord_t sy = lv_obj_get_scroll_y(obj); - lv_anim_set_values(&a, -sy, -sy + y); + lv_anim_set_values(&a, -sy, -sy + dy); lv_anim_set_exec_cb(&a, scroll_y_anim); lv_anim_set_path_cb(&a, lv_anim_path_ease_out); @@ -298,7 +347,7 @@ void lv_obj_scroll_by(lv_obj_t * obj, lv_coord_t x, lv_coord_t y, lv_anim_enable /*Remove pending animations*/ bool y_del = lv_anim_del(obj, scroll_y_anim); bool x_del = lv_anim_del(obj, scroll_x_anim); - scroll_by_raw(obj, x, y); + scroll_by_raw(obj, dx, dy); if(y_del || x_del) { lv_res_t res; res = lv_event_send(obj, LV_EVENT_SCROLL_END, NULL); @@ -317,48 +366,20 @@ void lv_obj_scroll_to_x(lv_obj_t * obj, lv_coord_t x, lv_anim_enable_t anim_en) { lv_anim_del(obj, scroll_x_anim); - /*Don't let scroll more then naturally possible by the size of the content*/ - if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) != LV_BASE_DIR_RTL) { - if(x < 0) x = 0; - if(x > 0) { - lv_coord_t scroll_max = lv_obj_get_scroll_left(obj) + lv_obj_get_scroll_right(obj); - if(scroll_max < 0) scroll_max = 0; - - if(x > scroll_max) x = scroll_max; - } - } - else { - if(x > 0) x = 0; - if(x < 0) { - lv_coord_t scroll_max = lv_obj_get_scroll_left(obj) + lv_obj_get_scroll_right(obj); - if(scroll_max < 0) scroll_max = 0; - - if(x < -scroll_max) x = -scroll_max; - } - } - lv_coord_t scroll_x = lv_obj_get_scroll_x(obj); lv_coord_t diff = -x + scroll_x; - lv_obj_scroll_by(obj, diff, 0, anim_en); + lv_obj_scroll_by_bounded(obj, diff, 0, anim_en); } void lv_obj_scroll_to_y(lv_obj_t * obj, lv_coord_t y, lv_anim_enable_t anim_en) { lv_anim_del(obj, scroll_y_anim); - /*Don't let scroll more then naturally possible by the size of the content*/ - if(y < 0) y = 0; - if(y > 0) { - lv_coord_t scroll_max = lv_obj_get_scroll_top(obj) + lv_obj_get_scroll_bottom(obj); - if(scroll_max < 0) scroll_max = 0; - if(y > scroll_max) y = scroll_max; - } - lv_coord_t scroll_y = lv_obj_get_scroll_y(obj); lv_coord_t diff = -y + scroll_y; - lv_obj_scroll_by(obj, 0, diff, anim_en); + lv_obj_scroll_by_bounded(obj, 0, diff, anim_en); } void lv_obj_scroll_to_view(lv_obj_t * obj, lv_anim_enable_t anim_en) diff --git a/src/core/lv_obj_scroll.h b/src/core/lv_obj_scroll.h index fc52e8eea..c9fdd720d 100644 --- a/src/core/lv_obj_scroll.h +++ b/src/core/lv_obj_scroll.h @@ -183,17 +183,27 @@ void lv_obj_get_scroll_end(struct _lv_obj_t * obj, lv_point_t * end); *====================*/ /** - * * Scroll by a given amount of pixels * @param obj pointer to an object to scroll - * @param x pixels to scroll horizontally - * @param y pixels to scroll vertically + * @param dx pixels to scroll horizontally + * @param dy pixels to scroll vertically * @param anim_en LV_ANIM_ON: scroll with animation; LV_ANIM_OFF: scroll immediately * @note > 0 value means scroll right/bottom (show the more content on the right/bottom) - * @note + * @note e.g. dy = -20 means scroll down 20 px */ void lv_obj_scroll_by(struct _lv_obj_t * obj, lv_coord_t x, lv_coord_t y, lv_anim_enable_t anim_en); +/** + * Scroll by a given amount of pixels. + * `dx` and `dy` will be limited internally to allow scrolling only on the content area. + * @param obj pointer to an object to scroll + * @param dx pixels to scroll horizontally + * @param dy pixels to scroll vertically + * @param anim_en LV_ANIM_ON: scroll with animation; LV_ANIM_OFF: scroll immediately + * @note e.g. dy = -20 means scroll down 20 px + */ +void lv_obj_scroll_by_bounded(struct _lv_obj_t * obj, lv_coord_t dx, lv_coord_t dy, lv_anim_enable_t anim_en); + /** * Scroll to a given coordinate on an object. * `x` and `y` will be limited internally to allow scrolling only on the content area. diff --git a/src/extra/others/gridnav/lv_gridnav.c b/src/extra/others/gridnav/lv_gridnav.c new file mode 100644 index 000000000..4c0c598d5 --- /dev/null +++ b/src/extra/others/gridnav/lv_gridnav.c @@ -0,0 +1,354 @@ +/** + * @file lv_gridnav.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_gridnav.h" +#if LV_USE_GRIDNAV + +#include "../../../misc/lv_assert.h" +#include "../../../misc/lv_math.h" +#include "../../../core/lv_indev.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_gridnav_ctrl_t ctrl; + lv_obj_t * focused_obj; +} lv_gridnav_dsc_t; + +typedef enum { + FIND_LEFT, + FIND_RIGHT, + FIND_TOP, + FIND_BOTTOM, + FIND_NEXT_ROW_FIRST_ITEM, + FIND_PREV_ROW_LAST_ITEM, + FIND_FIRST_ROW, + FIND_LAST_ROW, +} find_mode_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void gridnav_event_cb(lv_event_t * e); +static lv_obj_t * find_chid(lv_obj_t * obj, lv_obj_t * start_child, find_mode_t mode); +static lv_obj_t * find_first_focusable(lv_obj_t * obj); +static lv_obj_t * find_last_focusable(lv_obj_t * obj); +static bool obj_is_focuable(lv_obj_t * obj); +static lv_coord_t get_x_center(lv_obj_t * obj); +static lv_coord_t get_y_center(lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_gridnav_add(lv_obj_t * obj, lv_gridnav_ctrl_t ctrl) +{ + lv_gridnav_remove(obj); /*Be sure to not add gridnav twice*/ + + lv_gridnav_dsc_t * dsc = lv_mem_alloc(sizeof(lv_gridnav_dsc_t)); + LV_ASSERT_MALLOC(dsc); + dsc->ctrl = ctrl; + dsc->focused_obj = NULL; + lv_obj_add_event_cb(obj, gridnav_event_cb, LV_EVENT_ALL, dsc); + + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_WITH_ARROW); +} + +void lv_gridnav_remove(lv_obj_t * obj) +{ + lv_gridnav_dsc_t * dsc = lv_obj_get_event_user_data(obj, gridnav_event_cb); + if(dsc == NULL) return; /* no gridnav on this object */ + + lv_mem_free(dsc); + lv_obj_remove_event_cb(obj, gridnav_event_cb); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void gridnav_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_current_target(e); + lv_gridnav_dsc_t * dsc = lv_event_get_user_data(e); + lv_event_code_t code = lv_event_get_code(e); + + if(code == LV_EVENT_KEY) { + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + if(child_cnt == 0) return; + + if(dsc->focused_obj == NULL) dsc->focused_obj = find_first_focusable(obj); + if(dsc->focused_obj == NULL) return; + + uint32_t key = lv_indev_get_key(lv_indev_get_act()); + lv_obj_t * guess = NULL; + + if(key == LV_KEY_RIGHT) { + 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_coord_t d = lv_obj_get_width(dsc->focused_obj) / 4; + if(d <= 0) d = 1; + lv_obj_scroll_by_bounded(dsc->focused_obj, -d, 0, LV_ANIM_ON); + } + else { + guess = find_chid(obj, dsc->focused_obj, FIND_RIGHT); + if(guess == NULL) { + if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) { + guess = find_chid(obj, dsc->focused_obj, FIND_NEXT_ROW_FIRST_ITEM); + if(guess == NULL) guess = find_first_focusable(obj); + } + else { + lv_group_focus_next(lv_obj_get_group(obj)); + } + } + } + } + else if(key == LV_KEY_LEFT) { + 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_coord_t d = lv_obj_get_width(dsc->focused_obj) / 4; + if(d <= 0) d = 1; + lv_obj_scroll_by_bounded(dsc->focused_obj, d, 0, LV_ANIM_ON); + } + else { + guess = find_chid(obj, dsc->focused_obj, FIND_LEFT); + if(guess == NULL) { + if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) { + guess = find_chid(obj, dsc->focused_obj, FIND_PREV_ROW_LAST_ITEM); + if(guess == NULL) guess = find_last_focusable(obj); + } + else { + lv_group_focus_prev(lv_obj_get_group(obj)); + } + } + } + } + else if(key == LV_KEY_DOWN) { + 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_coord_t d = lv_obj_get_height(dsc->focused_obj) / 4; + if(d <= 0) d = 1; + lv_obj_scroll_by_bounded(dsc->focused_obj, 0, -d, LV_ANIM_ON); + } + else { + guess = find_chid(obj, dsc->focused_obj, FIND_BOTTOM); + if(guess == NULL) { + if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) { + guess = find_chid(obj, dsc->focused_obj, FIND_FIRST_ROW); + } + else { + lv_group_focus_next(lv_obj_get_group(obj)); + } + } + } + } + else if(key == LV_KEY_UP) { + 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_coord_t d = lv_obj_get_height(dsc->focused_obj) / 4; + if(d <= 0) d = 1; + lv_obj_scroll_by_bounded(dsc->focused_obj, 0, d, LV_ANIM_ON); + } + else { + guess = find_chid(obj, dsc->focused_obj, FIND_TOP); + if(guess == NULL) { + if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) { + guess = find_chid(obj, dsc->focused_obj, FIND_LAST_ROW); + } + else { + lv_group_focus_prev(lv_obj_get_group(obj)); + } + } + } + } + else { + if(lv_group_get_focused(lv_obj_get_group(obj)) == obj) { + lv_event_send(dsc->focused_obj, LV_EVENT_KEY, &key); + } + } + + if(guess && guess != dsc->focused_obj) { + lv_obj_clear_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_add_state(guess, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_scroll_to_view(guess, LV_ANIM_ON); + dsc->focused_obj = guess; + } + } + else if(code == LV_EVENT_FOCUSED) { + if(dsc->focused_obj == NULL) dsc->focused_obj = find_first_focusable(obj); + if(dsc->focused_obj) { + lv_obj_add_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_scroll_to_view(dsc->focused_obj, LV_ANIM_OFF); + } + } + else if(code == LV_EVENT_DEFOCUSED) { + if(dsc->focused_obj) { + lv_obj_clear_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + } + } + else if(code == LV_EVENT_CHILD_CREATED) { + lv_obj_t * child = lv_event_get_target(e); + if(lv_obj_get_parent(child) == obj) { + if(dsc->focused_obj == NULL) { + dsc->focused_obj = child; + if(lv_obj_has_state(obj, LV_STATE_FOCUSED)) { + lv_obj_add_state(child, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_scroll_to_view(child, LV_ANIM_OFF); + } + } + } + } + else if(code == LV_EVENT_CHILD_DELETED) { + /*This event bubble, so be sure this object's child was deleted. + *As we don't know which object was deleted we can't make the next focused. + *So make the first object focused*/ + lv_obj_t * target = lv_event_get_target(e); + if(target == obj) { + dsc->focused_obj = find_first_focusable(obj); + } + } + else if(code == LV_EVENT_DELETE) { + lv_gridnav_remove(obj); + } + else if(code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING || code == LV_EVENT_PRESS_LOST || + code == LV_EVENT_LONG_PRESSED || code == LV_EVENT_LONG_PRESSED_REPEAT || + code == LV_EVENT_CLICKED || code == LV_EVENT_RELEASED) { + if(lv_group_get_focused(lv_obj_get_group(obj)) == obj) { + /*Forward press/release related event too*/ + lv_indev_type_t t = lv_indev_get_type(lv_indev_get_act()); + if(t == LV_INDEV_TYPE_ENCODER || t == LV_INDEV_TYPE_KEYPAD) { + lv_event_send(dsc->focused_obj, code, lv_indev_get_act()); + } + } + } +} + +static lv_obj_t * find_chid(lv_obj_t * obj, lv_obj_t * start_child, find_mode_t mode) +{ + lv_coord_t x_start = get_x_center(start_child); + lv_coord_t y_start = get_y_center(start_child); + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + lv_obj_t * guess = NULL; + lv_coord_t x_err_guess = LV_COORD_MAX; + lv_coord_t y_err_guess = LV_COORD_MAX; + lv_coord_t h_half = lv_obj_get_height(start_child) / 2; + lv_coord_t h_max = lv_obj_get_height(obj) + lv_obj_get_scroll_top(obj) + lv_obj_get_scroll_bottom(obj); + uint32_t i; + for(i = 0; i < child_cnt; i++) { + lv_obj_t * child = lv_obj_get_child(obj, i); + if(child == start_child) continue; + if(obj_is_focuable(child) == false) continue; + + lv_coord_t x_err; + lv_coord_t y_err; + switch(mode) { + case FIND_LEFT: + x_err = get_x_center(child) - x_start; + y_err = get_y_center(child) - y_start; + if(x_err >= 0) continue; /*It's on the right*/ + if(LV_ABS(y_err) > h_half) continue; /*Too far*/ + break; + case FIND_RIGHT: + x_err = get_x_center(child) - x_start; + y_err = get_y_center(child) - y_start; + if(x_err <= 0) continue; /*It's on the left*/ + if(LV_ABS(y_err) > h_half) continue; /*Too far*/ + break; + case FIND_TOP: + x_err = get_x_center(child) - x_start; + y_err = get_y_center(child) - y_start; + if(y_err >= 0) continue; /*It's on the bottom*/ + break; + case FIND_BOTTOM: + x_err = get_x_center(child) - x_start; + y_err = get_y_center(child) - y_start; + if(y_err <= 0) continue; /*It's on the top*/ + break; + case FIND_NEXT_ROW_FIRST_ITEM: + y_err = get_y_center(child) - y_start; + if(y_err <= 0) continue; /*It's on the top*/ + x_err = lv_obj_get_x(child); + break; + case FIND_PREV_ROW_LAST_ITEM: + y_err = get_y_center(child) - y_start; + if(y_err >= 0) continue; /*It's on the bottom*/ + x_err = obj->coords.x2 - child->coords.x2; + break; + case FIND_FIRST_ROW: + x_err = get_x_center(child) - x_start; + y_err = lv_obj_get_y(child); + break; + case FIND_LAST_ROW: + x_err = get_x_center(child) - x_start; + y_err = h_max - lv_obj_get_y(child); + } + + if(guess == NULL || + (y_err * y_err + x_err * x_err < y_err_guess * y_err_guess + x_err_guess * x_err_guess)) { + guess = child; + x_err_guess = x_err; + y_err_guess = y_err; + } + } + return guess; +} + +static lv_obj_t * find_first_focusable(lv_obj_t * obj) +{ + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + uint32_t i; + for(i = 0; i < child_cnt; i++) { + lv_obj_t * child = lv_obj_get_child(obj, i); + if(obj_is_focuable(child)) return child; + + } + return NULL; +} + +static lv_obj_t * find_last_focusable(lv_obj_t * obj) +{ + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + int32_t i; + for(i = child_cnt - 1; i >= 0; i++) { + lv_obj_t * child = lv_obj_get_child(obj, i); + if(obj_is_focuable(child)) return child; + } + return NULL; +} + +static bool obj_is_focuable(lv_obj_t * obj) +{ + if(lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return false; + if(lv_obj_has_flag(obj, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_CLICK_FOCUSABLE)) return true; + else return false; +} + +static lv_coord_t get_x_center(lv_obj_t * obj) +{ + return obj->coords.x1 + lv_area_get_width(&obj->coords) / 2; +} + +static lv_coord_t get_y_center(lv_obj_t * obj) +{ + return obj->coords.y1 + lv_area_get_height(&obj->coords) / 2; +} + +#endif /*LV_USE_GRIDNAV*/ diff --git a/src/extra/others/gridnav/lv_gridnav.h b/src/extra/others/gridnav/lv_gridnav.h new file mode 100644 index 000000000..ea81595de --- /dev/null +++ b/src/extra/others/gridnav/lv_gridnav.h @@ -0,0 +1,115 @@ +/** + * @file lv_templ.c + * + */ + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/*This typedef exists purely to keep -Wpedantic happy when the file is empty.*/ +/*It can be removed.*/ +typedef int _keep_pedantic_happy; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ +/** + * @file lv_gridnav.h + * + */ + +#ifndef LV_GRIDFOCUS_H +#define LV_GRIDFOCUS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" + +#if LV_USE_GRIDNAV + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef enum { + LV_GRIDNAV_CTRL_NONE = 0x0, + + /** + * If there is no next/previous object in a direction, + * the focus goes to the object in the next/previous row (on left/right keys) + * or first/last row (on up/down keys) + */ + LV_GRIDNAV_CTRL_ROLLOVER = 0x1, + + /** + * If an arrow is pressed and the focused 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 */ + LV_GRIDNAV_CTRL_SCROLL_FIRST = 0x2, + +} lv_gridnav_ctrl_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Add grid navigation feature to an object. It expects the children to be arranged + * into a grid-like layout. Although it's not required to have pixel perfect alignment. + * This feature makes possible to use keys to navigate among the children and focus them. + * The keys other than arrows and press/release related events + * are forwarded to the focused child. + * @param obj pointer to an object on which navigation should be applied. + * @param ctrl control flags from `lv_gridnav_ctrl_t`. + */ +void lv_gridnav_add(lv_obj_t * obj, lv_gridnav_ctrl_t ctrl); + +/** + * Remove the grid navigation support from an object + * @param obj pointer to an object + */ +void lv_gridnav_remove(lv_obj_t * obj); + +/********************** + * MACROS + **********************/ +#endif /*LV_USE_GRIDNAV*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_GRIDFOCUS_H*/ diff --git a/src/extra/others/lv_others.h b/src/extra/others/lv_others.h index 370ed7d45..a4338666a 100644 --- a/src/extra/others/lv_others.h +++ b/src/extra/others/lv_others.h @@ -15,6 +15,7 @@ extern "C" { *********************/ #include "snapshot/lv_snapshot.h" #include "monkey/lv_monkey.h" +#include "gridnav/lv_gridnav.h" /********************* * DEFINES diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 853037c68..00c31367d 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -1991,14 +1991,10 @@ /*1: Enable API to take snapshot for object*/ #ifndef LV_USE_SNAPSHOT - #ifdef _LV_KCONFIG_PRESENT - #ifdef CONFIG_LV_USE_SNAPSHOT - #define LV_USE_SNAPSHOT CONFIG_LV_USE_SNAPSHOT - #else - #define LV_USE_SNAPSHOT 0 - #endif + #ifdef CONFIG_LV_USE_SNAPSHOT + #define LV_USE_SNAPSHOT CONFIG_LV_USE_SNAPSHOT #else - #define LV_USE_SNAPSHOT 1 + #define LV_USE_SNAPSHOT 0 #endif #endif @@ -2007,7 +2003,16 @@ #ifdef CONFIG_LV_USE_MONKEY #define LV_USE_MONKEY CONFIG_LV_USE_MONKEY #else - #define LV_USE_MONKEY 0 + #define LV_USE_MONKEY 0 + #endif +#endif + +/*1: Enable grid navigation*/ +#ifndef LV_USE_GRIDNAV + #ifdef CONFIG_LV_USE_GRIDNAV + #define LV_USE_GRIDNAV CONFIG_LV_USE_GRIDNAV + #else + #define LV_USE_GRIDNAV 0 #endif #endif