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