1 /**
2 * @file lv_gridnav.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_gridnav.h"
10 #if LV_USE_GRIDNAV
11
12 #include "../../../misc/lv_assert.h"
13 #include "../../../misc/lv_math.h"
14 #include "../../../core/lv_indev.h"
15
16 /*********************
17 * DEFINES
18 *********************/
19
20 /**********************
21 * TYPEDEFS
22 **********************/
23 typedef struct {
24 lv_gridnav_ctrl_t ctrl;
25 lv_obj_t * focused_obj;
26 } lv_gridnav_dsc_t;
27
28 typedef enum {
29 FIND_LEFT,
30 FIND_RIGHT,
31 FIND_TOP,
32 FIND_BOTTOM,
33 FIND_NEXT_ROW_FIRST_ITEM,
34 FIND_PREV_ROW_LAST_ITEM,
35 FIND_FIRST_ROW,
36 FIND_LAST_ROW,
37 } find_mode_t;
38
39 /**********************
40 * STATIC PROTOTYPES
41 **********************/
42 static void gridnav_event_cb(lv_event_t * e);
43 static lv_obj_t * find_chid(lv_obj_t * obj, lv_obj_t * start_child, find_mode_t mode);
44 static lv_obj_t * find_first_focusable(lv_obj_t * obj);
45 static lv_obj_t * find_last_focusable(lv_obj_t * obj);
46 static bool obj_is_focuable(lv_obj_t * obj);
47 static lv_coord_t get_x_center(lv_obj_t * obj);
48 static lv_coord_t get_y_center(lv_obj_t * obj);
49
50 /**********************
51 * STATIC VARIABLES
52 **********************/
53
54 /**********************
55 * MACROS
56 **********************/
57
58 /**********************
59 * GLOBAL FUNCTIONS
60 **********************/
61
lv_gridnav_add(lv_obj_t * obj,lv_gridnav_ctrl_t ctrl)62 void lv_gridnav_add(lv_obj_t * obj, lv_gridnav_ctrl_t ctrl)
63 {
64 lv_gridnav_remove(obj); /*Be sure to not add gridnav twice*/
65
66 lv_gridnav_dsc_t * dsc = lv_mem_alloc(sizeof(lv_gridnav_dsc_t));
67 LV_ASSERT_MALLOC(dsc);
68 dsc->ctrl = ctrl;
69 dsc->focused_obj = NULL;
70 lv_obj_add_event_cb(obj, gridnav_event_cb, LV_EVENT_ALL, dsc);
71
72 lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_WITH_ARROW);
73 }
74
lv_gridnav_remove(lv_obj_t * obj)75 void lv_gridnav_remove(lv_obj_t * obj)
76 {
77 lv_gridnav_dsc_t * dsc = lv_obj_get_event_user_data(obj, gridnav_event_cb);
78 if(dsc == NULL) return; /* no gridnav on this object */
79
80 lv_mem_free(dsc);
81 lv_obj_remove_event_cb(obj, gridnav_event_cb);
82 }
83
lv_gridnav_set_focused(lv_obj_t * cont,lv_obj_t * to_focus,lv_anim_enable_t anim_en)84 void lv_gridnav_set_focused(lv_obj_t * cont, lv_obj_t * to_focus, lv_anim_enable_t anim_en)
85 {
86 LV_ASSERT_NULL(to_focus);
87 lv_gridnav_dsc_t * dsc = lv_obj_get_event_user_data(cont, gridnav_event_cb);
88 if(dsc == NULL) {
89 LV_LOG_WARN("`cont` is not a gridnav container");
90 return;
91 }
92
93 if(obj_is_focuable(to_focus) == false) {
94 LV_LOG_WARN("The object to focus is not focusable");
95 return;
96 }
97
98 lv_obj_clear_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
99 lv_obj_add_state(to_focus, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
100 lv_obj_scroll_to_view(to_focus, anim_en);
101 dsc->focused_obj = to_focus;
102
103 }
104
105 /**********************
106 * STATIC FUNCTIONS
107 **********************/
108
gridnav_event_cb(lv_event_t * e)109 static void gridnav_event_cb(lv_event_t * e)
110 {
111 lv_obj_t * obj = lv_event_get_current_target(e);
112 lv_gridnav_dsc_t * dsc = lv_event_get_user_data(e);
113 lv_event_code_t code = lv_event_get_code(e);
114
115 if(code == LV_EVENT_KEY) {
116 uint32_t child_cnt = lv_obj_get_child_cnt(obj);
117 if(child_cnt == 0) return;
118
119 if(dsc->focused_obj == NULL) dsc->focused_obj = find_first_focusable(obj);
120 if(dsc->focused_obj == NULL) return;
121
122 uint32_t key = lv_event_get_key(e);
123 lv_obj_t * guess = NULL;
124
125 if(key == LV_KEY_RIGHT) {
126 if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) &&
127 lv_obj_get_scroll_right(dsc->focused_obj) > 0) {
128 lv_coord_t d = lv_obj_get_width(dsc->focused_obj) / 4;
129 if(d <= 0) d = 1;
130 lv_obj_scroll_by_bounded(dsc->focused_obj, -d, 0, LV_ANIM_ON);
131 }
132 else {
133 guess = find_chid(obj, dsc->focused_obj, FIND_RIGHT);
134 if(guess == NULL) {
135 if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) {
136 guess = find_chid(obj, dsc->focused_obj, FIND_NEXT_ROW_FIRST_ITEM);
137 if(guess == NULL) guess = find_first_focusable(obj);
138 }
139 else {
140 lv_group_focus_next(lv_obj_get_group(obj));
141 }
142 }
143 }
144 }
145 else if(key == LV_KEY_LEFT) {
146 if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) &&
147 lv_obj_get_scroll_left(dsc->focused_obj) > 0) {
148 lv_coord_t d = lv_obj_get_width(dsc->focused_obj) / 4;
149 if(d <= 0) d = 1;
150 lv_obj_scroll_by_bounded(dsc->focused_obj, d, 0, LV_ANIM_ON);
151 }
152 else {
153 guess = find_chid(obj, dsc->focused_obj, FIND_LEFT);
154 if(guess == NULL) {
155 if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) {
156 guess = find_chid(obj, dsc->focused_obj, FIND_PREV_ROW_LAST_ITEM);
157 if(guess == NULL) guess = find_last_focusable(obj);
158 }
159 else {
160 lv_group_focus_prev(lv_obj_get_group(obj));
161 }
162 }
163 }
164 }
165 else if(key == LV_KEY_DOWN) {
166 if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) &&
167 lv_obj_get_scroll_bottom(dsc->focused_obj) > 0) {
168 lv_coord_t d = lv_obj_get_height(dsc->focused_obj) / 4;
169 if(d <= 0) d = 1;
170 lv_obj_scroll_by_bounded(dsc->focused_obj, 0, -d, LV_ANIM_ON);
171 }
172 else {
173 guess = find_chid(obj, dsc->focused_obj, FIND_BOTTOM);
174 if(guess == NULL) {
175 if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) {
176 guess = find_chid(obj, dsc->focused_obj, FIND_FIRST_ROW);
177 }
178 else {
179 lv_group_focus_next(lv_obj_get_group(obj));
180 }
181 }
182 }
183 }
184 else if(key == LV_KEY_UP) {
185 if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) &&
186 lv_obj_get_scroll_top(dsc->focused_obj) > 0) {
187 lv_coord_t d = lv_obj_get_height(dsc->focused_obj) / 4;
188 if(d <= 0) d = 1;
189 lv_obj_scroll_by_bounded(dsc->focused_obj, 0, d, LV_ANIM_ON);
190 }
191 else {
192 guess = find_chid(obj, dsc->focused_obj, FIND_TOP);
193 if(guess == NULL) {
194 if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) {
195 guess = find_chid(obj, dsc->focused_obj, FIND_LAST_ROW);
196 }
197 else {
198 lv_group_focus_prev(lv_obj_get_group(obj));
199 }
200 }
201 }
202 }
203 else {
204 if(lv_group_get_focused(lv_obj_get_group(obj)) == obj) {
205 lv_event_send(dsc->focused_obj, LV_EVENT_KEY, &key);
206 }
207 }
208
209 if(guess && guess != dsc->focused_obj) {
210 lv_obj_clear_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
211 lv_obj_add_state(guess, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
212 lv_obj_scroll_to_view(guess, LV_ANIM_ON);
213 dsc->focused_obj = guess;
214 }
215 }
216 else if(code == LV_EVENT_FOCUSED) {
217 if(dsc->focused_obj == NULL) dsc->focused_obj = find_first_focusable(obj);
218 if(dsc->focused_obj) {
219 lv_obj_add_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
220 lv_obj_clear_state(dsc->focused_obj, LV_STATE_PRESSED); /*Be sure the focuses obj is not stuck in pressed state*/
221 lv_obj_scroll_to_view(dsc->focused_obj, LV_ANIM_OFF);
222 }
223 }
224 else if(code == LV_EVENT_DEFOCUSED) {
225 if(dsc->focused_obj) {
226 lv_obj_clear_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
227 }
228 }
229 else if(code == LV_EVENT_CHILD_CREATED) {
230 lv_obj_t * child = lv_event_get_target(e);
231 if(lv_obj_get_parent(child) == obj) {
232 if(dsc->focused_obj == NULL) {
233 dsc->focused_obj = child;
234 if(lv_obj_has_state(obj, LV_STATE_FOCUSED)) {
235 lv_obj_add_state(child, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
236 lv_obj_scroll_to_view(child, LV_ANIM_OFF);
237 }
238 }
239 }
240 }
241 else if(code == LV_EVENT_CHILD_DELETED) {
242 /*This event bubble, so be sure this object's child was deleted.
243 *As we don't know which object was deleted we can't make the next focused.
244 *So make the first object focused*/
245 lv_obj_t * target = lv_event_get_target(e);
246 if(target == obj) {
247 dsc->focused_obj = find_first_focusable(obj);
248 }
249 }
250 else if(code == LV_EVENT_DELETE) {
251 lv_gridnav_remove(obj);
252 }
253 else if(code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING || code == LV_EVENT_PRESS_LOST ||
254 code == LV_EVENT_LONG_PRESSED || code == LV_EVENT_LONG_PRESSED_REPEAT ||
255 code == LV_EVENT_CLICKED || code == LV_EVENT_RELEASED) {
256 if(lv_group_get_focused(lv_obj_get_group(obj)) == obj) {
257 /*Forward press/release related event too*/
258 lv_indev_type_t t = lv_indev_get_type(lv_indev_get_act());
259 if(t == LV_INDEV_TYPE_ENCODER || t == LV_INDEV_TYPE_KEYPAD) {
260 lv_event_send(dsc->focused_obj, code, lv_indev_get_act());
261 }
262 }
263 }
264 }
265
find_chid(lv_obj_t * obj,lv_obj_t * start_child,find_mode_t mode)266 static lv_obj_t * find_chid(lv_obj_t * obj, lv_obj_t * start_child, find_mode_t mode)
267 {
268 lv_coord_t x_start = get_x_center(start_child);
269 lv_coord_t y_start = get_y_center(start_child);
270 uint32_t child_cnt = lv_obj_get_child_cnt(obj);
271 lv_obj_t * guess = NULL;
272 lv_coord_t x_err_guess = LV_COORD_MAX;
273 lv_coord_t y_err_guess = LV_COORD_MAX;
274 lv_coord_t h_half = lv_obj_get_height(start_child) / 2;
275 lv_coord_t h_max = lv_obj_get_height(obj) + lv_obj_get_scroll_top(obj) + lv_obj_get_scroll_bottom(obj);
276 uint32_t i;
277 for(i = 0; i < child_cnt; i++) {
278 lv_obj_t * child = lv_obj_get_child(obj, i);
279 if(child == start_child) continue;
280 if(obj_is_focuable(child) == false) continue;
281
282 lv_coord_t x_err = 0;
283 lv_coord_t y_err = 0;
284 switch(mode) {
285 case FIND_LEFT:
286 x_err = get_x_center(child) - x_start;
287 y_err = get_y_center(child) - y_start;
288 if(x_err >= 0) continue; /*It's on the right*/
289 if(LV_ABS(y_err) > h_half) continue; /*Too far*/
290 break;
291 case FIND_RIGHT:
292 x_err = get_x_center(child) - x_start;
293 y_err = get_y_center(child) - y_start;
294 if(x_err <= 0) continue; /*It's on the left*/
295 if(LV_ABS(y_err) > h_half) continue; /*Too far*/
296 break;
297 case FIND_TOP:
298 x_err = get_x_center(child) - x_start;
299 y_err = get_y_center(child) - y_start;
300 if(y_err >= 0) continue; /*It's on the bottom*/
301 break;
302 case FIND_BOTTOM:
303 x_err = get_x_center(child) - x_start;
304 y_err = get_y_center(child) - y_start;
305 if(y_err <= 0) continue; /*It's on the top*/
306 break;
307 case FIND_NEXT_ROW_FIRST_ITEM:
308 y_err = get_y_center(child) - y_start;
309 if(y_err <= 0) continue; /*It's on the top*/
310 x_err = lv_obj_get_x(child);
311 break;
312 case FIND_PREV_ROW_LAST_ITEM:
313 y_err = get_y_center(child) - y_start;
314 if(y_err >= 0) continue; /*It's on the bottom*/
315 x_err = obj->coords.x2 - child->coords.x2;
316 break;
317 case FIND_FIRST_ROW:
318 x_err = get_x_center(child) - x_start;
319 y_err = lv_obj_get_y(child);
320 break;
321 case FIND_LAST_ROW:
322 x_err = get_x_center(child) - x_start;
323 y_err = h_max - lv_obj_get_y(child);
324 }
325
326 if(guess == NULL ||
327 (y_err * y_err + x_err * x_err < y_err_guess * y_err_guess + x_err_guess * x_err_guess)) {
328 guess = child;
329 x_err_guess = x_err;
330 y_err_guess = y_err;
331 }
332 }
333 return guess;
334 }
335
find_first_focusable(lv_obj_t * obj)336 static lv_obj_t * find_first_focusable(lv_obj_t * obj)
337 {
338 uint32_t child_cnt = lv_obj_get_child_cnt(obj);
339 uint32_t i;
340 for(i = 0; i < child_cnt; i++) {
341 lv_obj_t * child = lv_obj_get_child(obj, i);
342 if(obj_is_focuable(child)) return child;
343
344 }
345 return NULL;
346 }
347
find_last_focusable(lv_obj_t * obj)348 static lv_obj_t * find_last_focusable(lv_obj_t * obj)
349 {
350 uint32_t child_cnt = lv_obj_get_child_cnt(obj);
351 int32_t i;
352 for(i = child_cnt - 1; i >= 0; i--) {
353 lv_obj_t * child = lv_obj_get_child(obj, i);
354 if(obj_is_focuable(child)) return child;
355 }
356 return NULL;
357 }
358
obj_is_focuable(lv_obj_t * obj)359 static bool obj_is_focuable(lv_obj_t * obj)
360 {
361 if(lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return false;
362 if(lv_obj_has_flag(obj, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_CLICK_FOCUSABLE)) return true;
363 else return false;
364 }
365
get_x_center(lv_obj_t * obj)366 static lv_coord_t get_x_center(lv_obj_t * obj)
367 {
368 return obj->coords.x1 + lv_area_get_width(&obj->coords) / 2;
369 }
370
get_y_center(lv_obj_t * obj)371 static lv_coord_t get_y_center(lv_obj_t * obj)
372 {
373 return obj->coords.y1 + lv_area_get_height(&obj->coords) / 2;
374 }
375
376 #endif /*LV_USE_GRIDNAV*/
377