Skip to content

Commit

Permalink
feat(gridnav): add lv_gridnav (lvgl#2911)
Browse files Browse the repository at this point in the history
* add first implememtation

* Update src/extra/others/gridnav/lv_gridnav.c

Co-authored-by: embeddedt <[email protected]>

* 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 (lvgl#2994)

Co-authored-by: embeddedt <[email protected]>
Co-authored-by: Ken Carpenter <[email protected]>
  • Loading branch information
3 people committed Jan 20, 2022
1 parent 731ef5a commit 62fc712
Show file tree
Hide file tree
Showing 18 changed files with 948 additions and 53 deletions.
60 changes: 60 additions & 0 deletions docs/others/gridnav.md
Original file line number Diff line number Diff line change
@@ -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
```
1 change: 1 addition & 0 deletions docs/others/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
snapshot
monkey
gridnav
```

1 change: 1 addition & 0 deletions examples/layouts/flex/lv_example_flex_2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions examples/others/gridnav/index.rst
Original file line number Diff line number Diff line change
@@ -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
40 changes: 40 additions & 0 deletions examples/others/gridnav/lv_example_gridnav.h
Original file line number Diff line number Diff line change
@@ -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*/
72 changes: 72 additions & 0 deletions examples/others/gridnav/lv_example_gridnav_1.c
Original file line number Diff line number Diff line change
@@ -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
44 changes: 44 additions & 0 deletions examples/others/gridnav/lv_example_gridnav_2.c
Original file line number Diff line number Diff line change
@@ -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
101 changes: 101 additions & 0 deletions examples/others/gridnav/lv_example_gridnav_3.c
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 62fc712

Please sign in to comment.