mirror of
https://github.com/lvgl/lvgl.git
synced 2025-01-14 06:42:58 +08:00
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>
This commit is contained in:
parent
731ef5a75e
commit
62fc7123f1
60
docs/others/gridnav.md
Normal file
60
docs/others/gridnav.md
Normal file
@ -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
|
||||
|
||||
```
|
@ -12,5 +12,6 @@
|
||||
|
||||
snapshot
|
||||
monkey
|
||||
gridnav
|
||||
```
|
||||
|
||||
|
@ -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);
|
||||
|
18
examples/others/gridnav/index.rst
Normal file
18
examples/others/gridnav/index.rst
Normal file
@ -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
examples/others/gridnav/lv_example_gridnav.h
Normal file
40
examples/others/gridnav/lv_example_gridnav.h
Normal file
@ -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
examples/others/gridnav/lv_example_gridnav_1.c
Normal file
72
examples/others/gridnav/lv_example_gridnav_1.c
Normal file
@ -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
examples/others/gridnav/lv_example_gridnav_2.c
Normal file
44
examples/others/gridnav/lv_example_gridnav_2.c
Normal file
@ -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
examples/others/gridnav/lv_example_gridnav_3.c
Normal file
101
examples/others/gridnav/lv_example_gridnav_3.c
Normal file
@ -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
|
47
examples/others/gridnav/lv_example_gridnav_4.c
Normal file
47
examples/others/gridnav/lv_example_gridnav_4.c
Normal file
@ -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
|
@ -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
|
||||
|
@ -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*/
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
354
src/extra/others/gridnav/lv_gridnav.c
Normal file
354
src/extra/others/gridnav/lv_gridnav.c
Normal file
@ -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*/
|
115
src/extra/others/gridnav/lv_gridnav.h
Normal file
115
src/extra/others/gridnav/lv_gridnav.h
Normal file
@ -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*/
|
@ -15,6 +15,7 @@ extern "C" {
|
||||
*********************/
|
||||
#include "snapshot/lv_snapshot.h"
|
||||
#include "monkey/lv_monkey.h"
|
||||
#include "gridnav/lv_gridnav.h"
|
||||
|
||||
/*********************
|
||||
* DEFINES
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user