1 /**
2  * @file lv_group.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_group.h"
10 #if LV_USE_GROUP != 0
11 #include "../lv_themes/lv_theme.h"
12 #include <stddef.h>
13 #include "../lv_misc/lv_gc.h"
14 
15 #if defined(LV_GC_INCLUDE)
16 #include LV_GC_INCLUDE
17 #endif /* LV_ENABLE_GC */
18 
19 /*********************
20  *      DEFINES
21  *********************/
22 
23 /**********************
24  *      TYPEDEFS
25  **********************/
26 
27 /**********************
28  *  STATIC PROTOTYPES
29  **********************/
30 static void style_mod_def(lv_group_t * group, lv_style_t * style);
31 static void style_mod_edit_def(lv_group_t * group, lv_style_t * style);
32 static void refresh_theme(lv_group_t * g, lv_theme_t * th);
33 static void focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *),
34                             void * (*move)(const lv_ll_t *, const void *));
35 static void lv_group_refocus(lv_group_t * g);
36 static void obj_to_foreground(lv_obj_t * obj);
37 
38 /**********************
39  *  STATIC VARIABLES
40  **********************/
41 
42 /**********************
43  *      MACROS
44  **********************/
45 
46 /**********************
47  *   GLOBAL FUNCTIONS
48  **********************/
49 
50 /**
51  * Init. the group module
52  */
lv_group_init(void)53 void lv_group_init(void)
54 {
55     lv_ll_init(&LV_GC_ROOT(_lv_group_ll), sizeof(lv_group_t));
56 }
57 
58 /**
59  * Create a new object group
60  * @return pointer to the new object group
61  */
lv_group_create(void)62 lv_group_t * lv_group_create(void)
63 {
64     lv_group_t * group = lv_ll_ins_head(&LV_GC_ROOT(_lv_group_ll));
65     lv_mem_assert(group);
66     if(group == NULL) return NULL;
67     lv_ll_init(&group->obj_ll, sizeof(lv_obj_t *));
68 
69     group->obj_focus      = NULL;
70     group->frozen         = 0;
71     group->focus_cb       = NULL;
72     group->click_focus    = 1;
73     group->editing        = 0;
74     group->refocus_policy = LV_GROUP_REFOCUS_POLICY_PREV;
75     group->wrap           = 1;
76 
77 #if LV_USE_USER_DATA
78     memset(&group->user_data, 0, sizeof(lv_group_user_data_t));
79 #endif
80 
81     /*Initialize style modification callbacks from current theme*/
82     refresh_theme(group, lv_theme_get_current());
83 
84     return group;
85 }
86 
87 /**
88  * Delete a group object
89  * @param group pointer to a group
90  */
lv_group_del(lv_group_t * group)91 void lv_group_del(lv_group_t * group)
92 {
93     /*Defocus the the currently focused object*/
94     if(group->obj_focus != NULL) {
95         (*group->obj_focus)->signal_cb(*group->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
96         lv_obj_invalidate(*group->obj_focus);
97     }
98 
99     /*Remove the objects from the group*/
100     lv_obj_t ** obj;
101     LV_LL_READ(group->obj_ll, obj)
102     {
103         (*obj)->group_p = NULL;
104     }
105 
106     lv_ll_clear(&(group->obj_ll));
107     lv_ll_rem(&LV_GC_ROOT(_lv_group_ll), group);
108     lv_mem_free(group);
109 }
110 
111 /**
112  * Add an object to a group
113  * @param group pointer to a group
114  * @param obj pointer to an object to add
115  */
lv_group_add_obj(lv_group_t * group,lv_obj_t * obj)116 void lv_group_add_obj(lv_group_t * group, lv_obj_t * obj)
117 {
118     if(group == NULL) return;
119 
120     /*Do not add the object twice*/
121     lv_obj_t ** obj_i;
122     LV_LL_READ(group->obj_ll, obj_i)
123     {
124         if((*obj_i) == obj) {
125             LV_LOG_INFO("lv_group_add_obj: the object is already added to this group");
126             return;
127         }
128     }
129 
130     /*If the object is already in a group and focused then defocus it*/
131     if(obj->group_p) {
132         if(lv_obj_is_focused(obj)) {
133             lv_group_refocus(obj->group_p);
134 
135             LV_LOG_INFO("lv_group_add_obj: assign object to an other group");
136         }
137     }
138 
139     obj->group_p     = group;
140     lv_obj_t ** next = lv_ll_ins_tail(&group->obj_ll);
141     lv_mem_assert(next);
142     if(next == NULL) return;
143     *next = obj;
144 
145     /* If the head and the tail is equal then there is only one object in the linked list.
146      * In this case automatically activate it*/
147     if(lv_ll_get_head(&group->obj_ll) == next) {
148         lv_group_refocus(group);
149     }
150 }
151 
152 /**
153  * Remove an object from its group
154  * @param obj pointer to an object to remove
155  */
lv_group_remove_obj(lv_obj_t * obj)156 void lv_group_remove_obj(lv_obj_t * obj)
157 {
158     lv_group_t * g = obj->group_p;
159     if(g == NULL) return;
160     if(g->obj_focus == NULL) return; /*Just to be sure (Not possible if there is at least one object in the group)*/
161 
162     /*Focus on the next object*/
163     if(*g->obj_focus == obj) {
164         /*If this is the only object in the group then focus to nothing.*/
165         if(lv_ll_get_head(&g->obj_ll) == g->obj_focus && lv_ll_get_tail(&g->obj_ll) == g->obj_focus) {
166             (*g->obj_focus)->signal_cb(*g->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
167         }
168         /*If there more objects in the group then focus to the next/prev object*/
169         else {
170             lv_group_refocus(g);
171         }
172     }
173 
174     /* If the focuses object is still the same then it was the only object in the group but it will
175      * be deleted. Set the `obj_focus` to NULL to get back to the initial state of the group with
176      * zero objects*/
177     if(*g->obj_focus == obj) {
178         g->obj_focus = NULL;
179     }
180 
181     /*Search the object and remove it from its group */
182     lv_obj_t ** i;
183     LV_LL_READ(g->obj_ll, i)
184     {
185         if(*i == obj) {
186             lv_ll_rem(&g->obj_ll, i);
187             lv_mem_free(i);
188             obj->group_p = NULL;
189             break;
190         }
191     }
192 }
193 
194 /**
195  * Remove all objects from a group
196  * @param group pointer to a group
197  */
lv_group_remove_all_objs(lv_group_t * group)198 void lv_group_remove_all_objs(lv_group_t * group)
199 {
200     /*Defocus the the currently focused object*/
201     if(group->obj_focus != NULL) {
202         (*group->obj_focus)->signal_cb(*group->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
203         lv_obj_invalidate(*group->obj_focus);
204         group->obj_focus = NULL;
205     }
206 
207     /*Remove the objects from the group*/
208     lv_obj_t ** obj;
209     LV_LL_READ(group->obj_ll, obj)
210     {
211         (*obj)->group_p = NULL;
212     }
213 
214     lv_ll_clear(&(group->obj_ll));
215 }
216 
217 /**
218  * Focus on an object (defocus the current)
219  * @param obj pointer to an object to focus on
220  */
lv_group_focus_obj(lv_obj_t * obj)221 void lv_group_focus_obj(lv_obj_t * obj)
222 {
223     if(obj == NULL) return;
224     lv_group_t * g = obj->group_p;
225     if(g == NULL) return;
226 
227     if(g->frozen != 0) return;
228 
229     /*On defocus edit mode must be leaved*/
230     lv_group_set_editing(g, false);
231 
232     lv_obj_t ** i;
233     LV_LL_READ(g->obj_ll, i)
234     {
235         if(*i == obj) {
236             if(g->obj_focus != NULL) {
237                 (*g->obj_focus)->signal_cb(*g->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
238                 lv_res_t res = lv_event_send(*g->obj_focus, LV_EVENT_DEFOCUSED, NULL);
239                 if(res != LV_RES_OK) return;
240                 lv_obj_invalidate(*g->obj_focus);
241             }
242 
243             g->obj_focus = i;
244 
245             if(g->obj_focus != NULL) {
246                 (*g->obj_focus)->signal_cb(*g->obj_focus, LV_SIGNAL_FOCUS, NULL);
247                 if(g->focus_cb) g->focus_cb(g);
248                 lv_res_t res = lv_event_send(*g->obj_focus, LV_EVENT_FOCUSED, NULL);
249                 if(res != LV_RES_OK) return;
250                 lv_obj_invalidate(*g->obj_focus);
251 
252                 /*If the object or its parent has `top == true` bring it to the foregorund*/
253                 obj_to_foreground(*g->obj_focus);
254             }
255             break;
256         }
257     }
258 }
259 
260 /**
261  * Focus the next object in a group (defocus the current)
262  * @param group pointer to a group
263  */
lv_group_focus_next(lv_group_t * group)264 void lv_group_focus_next(lv_group_t * group)
265 {
266     focus_next_core(group, lv_ll_get_head, lv_ll_get_next);
267 }
268 
269 /**
270  * Focus the previous object in a group (defocus the current)
271  * @param group pointer to a group
272  */
lv_group_focus_prev(lv_group_t * group)273 void lv_group_focus_prev(lv_group_t * group)
274 {
275     focus_next_core(group, lv_ll_get_tail, lv_ll_get_prev);
276 }
277 
278 /**
279  * Do not let to change the focus from the current object
280  * @param group pointer to a group
281  * @param en true: freeze, false: release freezing (normal mode)
282  */
lv_group_focus_freeze(lv_group_t * group,bool en)283 void lv_group_focus_freeze(lv_group_t * group, bool en)
284 {
285     if(en == false)
286         group->frozen = 0;
287     else
288         group->frozen = 1;
289 }
290 
291 /**
292  * Send a control character to the focuses object of a group
293  * @param group pointer to a group
294  * @param c a character (use LV_KEY_.. to navigate)
295  * @return result of focused object in group.
296  */
lv_group_send_data(lv_group_t * group,uint32_t c)297 lv_res_t lv_group_send_data(lv_group_t * group, uint32_t c)
298 {
299     lv_obj_t * act = lv_group_get_focused(group);
300     if(act == NULL) return LV_RES_OK;
301 
302     lv_res_t res;
303 
304     res = act->signal_cb(act, LV_SIGNAL_CONTROL, &c);
305     if(res != LV_RES_OK) return res;
306 
307     res = lv_event_send(act, LV_EVENT_KEY, &c);
308     if(res != LV_RES_OK) return res;
309 
310     return res;
311 }
312 
313 /**
314  * Set a function for a group which will modify the object's style if it is in focus
315  * @param group pointer to a group
316  * @param style_mod_cb the style modifier function pointer
317  */
lv_group_set_style_mod_cb(lv_group_t * group,lv_group_style_mod_cb_t style_mod_cb)318 void lv_group_set_style_mod_cb(lv_group_t * group, lv_group_style_mod_cb_t style_mod_cb)
319 {
320     group->style_mod_cb = style_mod_cb;
321     if(group->obj_focus != NULL) lv_obj_invalidate(*group->obj_focus);
322 }
323 
324 /**
325  * Set a function for a group which will modify the object's style if it is in focus in edit mode
326  * @param group pointer to a group
327  * @param style_mod_func the style modifier function pointer
328  */
lv_group_set_style_mod_edit_cb(lv_group_t * group,lv_group_style_mod_cb_t style_mod_edit_cb)329 void lv_group_set_style_mod_edit_cb(lv_group_t * group, lv_group_style_mod_cb_t style_mod_edit_cb)
330 {
331     group->style_mod_edit_cb = style_mod_edit_cb;
332     if(group->obj_focus != NULL) lv_obj_invalidate(*group->obj_focus);
333 }
334 
335 /**
336  * Set a function for a group which will be called when a new object is focused
337  * @param group pointer to a group
338  * @param focus_cb the call back function or NULL if unused
339  */
lv_group_set_focus_cb(lv_group_t * group,lv_group_focus_cb_t focus_cb)340 void lv_group_set_focus_cb(lv_group_t * group, lv_group_focus_cb_t focus_cb)
341 {
342     group->focus_cb = focus_cb;
343 }
344 
345 /**
346  * Manually set the current mode (edit or navigate).
347  * @param group pointer to group
348  * @param edit: true: edit mode; false: navigate mode
349  */
lv_group_set_editing(lv_group_t * group,bool edit)350 void lv_group_set_editing(lv_group_t * group, bool edit)
351 {
352     uint8_t en_val = edit ? 1 : 0;
353 
354     if(en_val == group->editing) return; /*Do not set the same mode again*/
355 
356     group->editing     = en_val;
357     lv_obj_t * focused = lv_group_get_focused(group);
358 
359     if(focused) {
360         focused->signal_cb(focused, LV_SIGNAL_FOCUS, NULL); /*Focus again to properly leave/open edit/navigate mode*/
361         lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_FOCUSED, NULL);
362         if(res != LV_RES_OK) return;
363     }
364 
365     lv_obj_invalidate(focused);
366 }
367 
368 /**
369  * Set the `click_focus` attribute. If enabled then the object will be focused then it is clicked.
370  * @param group pointer to group
371  * @param en: true: enable `click_focus`
372  */
lv_group_set_click_focus(lv_group_t * group,bool en)373 void lv_group_set_click_focus(lv_group_t * group, bool en)
374 {
375     group->click_focus = en ? 1 : 0;
376 }
377 
lv_group_set_refocus_policy(lv_group_t * group,lv_group_refocus_policy_t policy)378 void lv_group_set_refocus_policy(lv_group_t * group, lv_group_refocus_policy_t policy)
379 {
380     group->refocus_policy = policy & 0x01;
381 }
382 
383 /**
384  * Set whether focus next/prev will allow wrapping from first->last or last->first.
385  * @param group pointer to group
386  * @param en: true: enable `wrap`
387  */
lv_group_set_wrap(lv_group_t * group,bool en)388 void lv_group_set_wrap(lv_group_t * group, bool en)
389 {
390     group->wrap = en ? 1 : 0;
391 }
392 
393 /**
394  * Modify a style with the set 'style_mod' function. The input style remains unchanged.
395  * @param group pointer to group
396  * @param style pointer to a style to modify
397  * @return a copy of the input style but modified with the 'style_mod' function
398  */
lv_group_mod_style(lv_group_t * group,const lv_style_t * style)399 lv_style_t * lv_group_mod_style(lv_group_t * group, const lv_style_t * style)
400 {
401     /*Load the current style. It will be modified by the callback*/
402     lv_style_copy(&group->style_tmp, style);
403 
404     if(group->editing) {
405         if(group->style_mod_edit_cb) group->style_mod_edit_cb(group, &group->style_tmp);
406     } else {
407         if(group->style_mod_cb) group->style_mod_cb(group, &group->style_tmp);
408     }
409     return &group->style_tmp;
410 }
411 
412 /**
413  * Get the focused object or NULL if there isn't one
414  * @param group pointer to a group
415  * @return pointer to the focused object
416  */
lv_group_get_focused(const lv_group_t * group)417 lv_obj_t * lv_group_get_focused(const lv_group_t * group)
418 {
419     if(!group) return NULL;
420     if(group->obj_focus == NULL) return NULL;
421 
422     return *group->obj_focus;
423 }
424 
425 #if LV_USE_USER_DATA
426 /**
427  * Get a pointer to the group's user data
428  * @param group pointer to an group
429  * @return pointer to the user data
430  */
lv_group_get_user_data(lv_group_t * group)431 lv_group_user_data_t * lv_group_get_user_data(lv_group_t * group)
432 {
433     return &group->user_data;
434 }
435 #endif
436 
437 /**
438  * Get a the style modifier function of a group
439  * @param group pointer to a group
440  * @return pointer to the style modifier function
441  */
lv_group_get_style_mod_cb(const lv_group_t * group)442 lv_group_style_mod_cb_t lv_group_get_style_mod_cb(const lv_group_t * group)
443 {
444     if(!group) return false;
445     return group->style_mod_cb;
446 }
447 
448 /**
449  * Get a the style modifier function of a group in edit mode
450  * @param group pointer to a group
451  * @return pointer to the style modifier function
452  */
lv_group_get_style_mod_edit_cb(const lv_group_t * group)453 lv_group_style_mod_cb_t lv_group_get_style_mod_edit_cb(const lv_group_t * group)
454 {
455     if(!group) return false;
456     return group->style_mod_edit_cb;
457 }
458 
459 /**
460  * Get the focus callback function of a group
461  * @param group pointer to a group
462  * @return the call back function or NULL if not set
463  */
lv_group_get_focus_cb(const lv_group_t * group)464 lv_group_focus_cb_t lv_group_get_focus_cb(const lv_group_t * group)
465 {
466     if(!group) return false;
467     return group->focus_cb;
468 }
469 
470 /**
471  * Get the current mode (edit or navigate).
472  * @param group pointer to group
473  * @return true: edit mode; false: navigate mode
474  */
lv_group_get_editing(const lv_group_t * group)475 bool lv_group_get_editing(const lv_group_t * group)
476 {
477     if(!group) return false;
478     return group->editing ? true : false;
479 }
480 
481 /**
482  * Get the `click_focus` attribute.
483  * @param group pointer to group
484  * @return true: `click_focus` is enabled; false: disabled
485  */
lv_group_get_click_focus(const lv_group_t * group)486 bool lv_group_get_click_focus(const lv_group_t * group)
487 {
488     if(!group) return false;
489     return group->click_focus ? true : false;
490 }
491 
492 /**
493  * Get whether focus next/prev will allow wrapping from first->last or last->first object.
494  * @param group pointer to group
495  * @param en: true: wrapping enabled; false: wrapping disabled
496  */
lv_group_get_wrap(lv_group_t * group)497 bool lv_group_get_wrap(lv_group_t * group)
498 {
499     if(!group) return false;
500     return group->wrap ? true : false;
501 }
502 
503 /**
504  * Notify the group that current theme changed and style modification callbacks need to be
505  * refreshed.
506  * @param group pointer to group. If NULL then all groups are notified.
507  */
lv_group_report_style_mod(lv_group_t * group)508 void lv_group_report_style_mod(lv_group_t * group)
509 {
510     lv_theme_t * th = lv_theme_get_current();
511 
512     if(group != NULL) {
513         refresh_theme(group, th);
514         return;
515     }
516 
517     lv_group_t * i;
518     LV_LL_READ(LV_GC_ROOT(_lv_group_ll), i)
519     {
520         refresh_theme(i, th);
521     }
522 }
523 
524 /**********************
525  *   STATIC FUNCTIONS
526  **********************/
527 
lv_group_refocus(lv_group_t * g)528 static void lv_group_refocus(lv_group_t * g)
529 {
530     /*Refocus must temporarily allow wrapping to work correctly*/
531     uint8_t temp_wrap = g->wrap;
532     g->wrap           = 1;
533 
534     if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_NEXT)
535         lv_group_focus_next(g);
536     else if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_PREV)
537         lv_group_focus_prev(g);
538     /*Restore wrap property*/
539     g->wrap = temp_wrap;
540 }
541 
542 /**
543  * Default style modifier function
544  * @param group pointer to the caller group
545  * @param style pointer to a style to modify. (Typically group.style_tmp) It will be OVERWRITTEN.
546  */
style_mod_def(lv_group_t * group,lv_style_t * style)547 static void style_mod_def(lv_group_t * group, lv_style_t * style)
548 {
549     (void)group; /*Unused*/
550 #if LV_COLOR_DEPTH != 1
551 
552     /*Make the style to be a little bit orange*/
553     style->body.border.opa   = LV_OPA_COVER;
554     style->body.border.color = LV_COLOR_ORANGE;
555 
556     /*If not transparent or has border then emphasis the border*/
557     if(style->body.opa != LV_OPA_TRANSP || style->body.border.width != 0) style->body.border.width = LV_DPI / 20;
558 
559     style->body.main_color   = lv_color_mix(style->body.main_color, LV_COLOR_ORANGE, LV_OPA_70);
560     style->body.grad_color   = lv_color_mix(style->body.grad_color, LV_COLOR_ORANGE, LV_OPA_70);
561     style->body.shadow.color = lv_color_mix(style->body.shadow.color, LV_COLOR_ORANGE, LV_OPA_60);
562 
563     style->text.color = lv_color_mix(style->text.color, LV_COLOR_ORANGE, LV_OPA_70);
564 
565     /*Add some recolor to the images*/
566     if(style->image.intense < LV_OPA_MIN) {
567         style->image.color   = LV_COLOR_ORANGE;
568         style->image.intense = LV_OPA_40;
569     }
570 #else
571     style->body.border.opa   = LV_OPA_COVER;
572     style->body.border.color = LV_COLOR_BLACK;
573     style->body.border.width = 2;
574 
575 #endif
576 }
577 
578 /**
579  * Default style modifier function
580  * @param group pointer to the caller group
581  * @param style pointer to a style to modify. (Typically group.style_tmp) It will be OVERWRITTEN.
582  */
style_mod_edit_def(lv_group_t * group,lv_style_t * style)583 static void style_mod_edit_def(lv_group_t * group, lv_style_t * style)
584 {
585     (void)group; /*Unused*/
586 #if LV_COLOR_DEPTH != 1
587 
588     /*Make the style to be a little bit orange*/
589     style->body.border.opa   = LV_OPA_COVER;
590     style->body.border.color = LV_COLOR_GREEN;
591 
592     /*If not empty or has border then emphasis the border*/
593     if(style->body.opa != LV_OPA_TRANSP || style->body.border.width != 0) style->body.border.width = LV_DPI / 20;
594 
595     style->body.main_color   = lv_color_mix(style->body.main_color, LV_COLOR_GREEN, LV_OPA_70);
596     style->body.grad_color   = lv_color_mix(style->body.grad_color, LV_COLOR_GREEN, LV_OPA_70);
597     style->body.shadow.color = lv_color_mix(style->body.shadow.color, LV_COLOR_GREEN, LV_OPA_60);
598 
599     style->text.color = lv_color_mix(style->text.color, LV_COLOR_GREEN, LV_OPA_70);
600 
601     /*Add some recolor to the images*/
602     if(style->image.intense < LV_OPA_MIN) {
603         style->image.color   = LV_COLOR_GREEN;
604         style->image.intense = LV_OPA_40;
605     }
606 
607 #else
608     style->body.border.opa   = LV_OPA_COVER;
609     style->body.border.color = LV_COLOR_BLACK;
610     style->body.border.width = 3;
611 
612 #endif
613 }
614 
refresh_theme(lv_group_t * g,lv_theme_t * th)615 static void refresh_theme(lv_group_t * g, lv_theme_t * th)
616 {
617     g->style_mod_cb      = style_mod_def;
618     g->style_mod_edit_cb = style_mod_edit_def;
619     if(th) {
620         if(th->group.style_mod_xcb) g->style_mod_cb = th->group.style_mod_xcb;
621         if(th->group.style_mod_edit_xcb) g->style_mod_edit_cb = th->group.style_mod_edit_xcb;
622     }
623 }
624 
focus_next_core(lv_group_t * group,void * (* begin)(const lv_ll_t *),void * (* move)(const lv_ll_t *,const void *))625 static void focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *),
626                             void * (*move)(const lv_ll_t *, const void *))
627 {
628     if(group->frozen) return;
629 
630     lv_obj_t ** obj_next     = group->obj_focus;
631     lv_obj_t ** obj_sentinel = NULL;
632     bool can_move            = true;
633     bool can_begin           = true;
634 
635     for(;;) {
636         if(obj_next == NULL) {
637             if(group->wrap || obj_sentinel == NULL) {
638                 if(!can_begin) return;
639                 obj_next  = begin(&group->obj_ll);
640                 can_move  = false;
641                 can_begin = false;
642             } else {
643                 /*Currently focused object is the last/first in the group, keep it that way*/
644                 return;
645             }
646         }
647 
648         if(obj_sentinel == NULL) {
649             obj_sentinel = obj_next;
650             if(obj_sentinel == NULL) return; /*Group is empty*/
651         }
652 
653         if(can_move) {
654             obj_next = move(&group->obj_ll, obj_next);
655 
656             /*Give up if we walked the entire list and haven't found another visible object*/
657             if(obj_next == obj_sentinel) return;
658         }
659 
660         can_move = true;
661 
662         if(obj_next == NULL) continue;
663 
664         /*Hidden objects don't receive focus*/
665         if(!lv_obj_get_hidden(*obj_next)) break;
666     }
667 
668     if(obj_next == group->obj_focus) return; /*There's only one visible object and it's already focused*/
669 
670     if(group->obj_focus) {
671         (*group->obj_focus)->signal_cb(*group->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
672         lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_DEFOCUSED, NULL);
673         if(res != LV_RES_OK) return;
674         lv_obj_invalidate(*group->obj_focus);
675     }
676 
677     group->obj_focus = obj_next;
678 
679     (*group->obj_focus)->signal_cb(*group->obj_focus, LV_SIGNAL_FOCUS, NULL);
680     lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_FOCUSED, NULL);
681     if(res != LV_RES_OK) return;
682 
683     /*If the object or its parent has `top == true` bring it to the foregorund*/
684     obj_to_foreground(*group->obj_focus);
685 
686     lv_obj_invalidate(*group->obj_focus);
687 
688     if(group->focus_cb) group->focus_cb(group);
689 }
690 
obj_to_foreground(lv_obj_t * obj)691 static void obj_to_foreground(lv_obj_t * obj)
692 {
693     /*Search for 'top' attribute*/
694     lv_obj_t * i        = obj;
695     lv_obj_t * last_top = NULL;
696     while(i != NULL) {
697         if(i->top != 0) last_top = i;
698         i = lv_obj_get_parent(i);
699     }
700 
701     if(last_top != NULL) {
702         /*Move the last_top object to the foreground*/
703         lv_obj_move_foreground(last_top);
704     }
705 }
706 
707 #endif /*LV_USE_GROUP != 0*/
708