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 <stddef.h>
12 #include "../lv_misc/lv_debug.h"
13 #include "../lv_themes/lv_theme.h"
14 #include "../lv_misc/lv_gc.h"
15 
16 #if defined(LV_GC_INCLUDE)
17     #include LV_GC_INCLUDE
18 #endif /* LV_ENABLE_GC */
19 
20 /*********************
21  *      DEFINES
22  *********************/
23 
24 /**********************
25  *      TYPEDEFS
26  **********************/
27 
28 /**********************
29  *  STATIC PROTOTYPES
30  **********************/
31 static void focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *),
32                             void * (*move)(const lv_ll_t *, const void *));
33 static void lv_group_refocus(lv_group_t * g);
34 static void obj_to_foreground(lv_obj_t * obj);
35 
36 /**********************
37  *  STATIC VARIABLES
38  **********************/
39 
40 /**********************
41  *      MACROS
42  **********************/
43 
44 /**********************
45  *   GLOBAL FUNCTIONS
46  **********************/
47 
48 /**
49  * Init. the group module
50  */
_lv_group_init(void)51 void _lv_group_init(void)
52 {
53     _lv_ll_init(&LV_GC_ROOT(_lv_group_ll), sizeof(lv_group_t));
54 }
55 
56 /**
57  * Create a new object group
58  * @return pointer to the new object group
59  */
lv_group_create(void)60 lv_group_t * lv_group_create(void)
61 {
62     lv_group_t * group = _lv_ll_ins_head(&LV_GC_ROOT(_lv_group_ll));
63     LV_ASSERT_MEM(group);
64     if(group == NULL) return NULL;
65     _lv_ll_init(&group->obj_ll, sizeof(lv_obj_t *));
66 
67     group->obj_focus      = NULL;
68     group->frozen         = 0;
69     group->focus_cb       = NULL;
70     group->click_focus    = 1;
71     group->editing        = 0;
72     group->refocus_policy = LV_GROUP_REFOCUS_POLICY_PREV;
73     group->wrap           = 1;
74 
75 #if LV_USE_USER_DATA
76     _lv_memset_00(&group->user_data, sizeof(lv_group_user_data_t));
77 #endif
78 
79     return group;
80 }
81 
82 /**
83  * Delete a group object
84  * @param group pointer to a group
85  */
lv_group_del(lv_group_t * group)86 void lv_group_del(lv_group_t * group)
87 {
88     /*Defocus the the currently focused object*/
89     if(group->obj_focus != NULL) {
90         (*group->obj_focus)->signal_cb(*group->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
91         lv_obj_invalidate(*group->obj_focus);
92     }
93 
94     /*Remove the objects from the group*/
95     lv_obj_t ** obj;
96     _LV_LL_READ(group->obj_ll, obj) {
97         (*obj)->group_p = NULL;
98     }
99 
100     _lv_ll_clear(&(group->obj_ll));
101     _lv_ll_remove(&LV_GC_ROOT(_lv_group_ll), group);
102     lv_mem_free(group);
103 }
104 
105 /**
106  * Add an object to a group
107  * @param group pointer to a group
108  * @param obj pointer to an object to add
109  */
lv_group_add_obj(lv_group_t * group,lv_obj_t * obj)110 void lv_group_add_obj(lv_group_t * group, lv_obj_t * obj)
111 {
112     if(group == NULL) return;
113     /*Do not add the object twice*/
114     lv_obj_t ** obj_i;
115     _LV_LL_READ(group->obj_ll, obj_i) {
116         if((*obj_i) == obj) {
117             LV_LOG_INFO("lv_group_add_obj: the object is already added to this group");
118             return;
119         }
120     }
121 
122     /*If the object is already in a group and focused then defocus it*/
123     if(obj->group_p) {
124         if(lv_obj_is_focused(obj)) {
125             lv_group_refocus(obj->group_p);
126 
127             LV_LOG_INFO("lv_group_add_obj: assign object to an other group");
128         }
129     }
130 
131     obj->group_p     = group;
132     lv_obj_t ** next = _lv_ll_ins_tail(&group->obj_ll);
133     LV_ASSERT_MEM(next);
134     if(next == NULL) return;
135     *next = obj;
136 
137     /* If the head and the tail is equal then there is only one object in the linked list.
138      * In this case automatically activate it*/
139     if(_lv_ll_get_head(&group->obj_ll) == next) {
140         lv_group_refocus(group);
141     }
142 }
143 
144 /**
145  * Remove an object from its group
146  * @param obj pointer to an object to remove
147  */
lv_group_remove_obj(lv_obj_t * obj)148 void lv_group_remove_obj(lv_obj_t * obj)
149 {
150     lv_group_t * g = obj->group_p;
151     if(g == NULL) return;
152     if(g->obj_focus == NULL) return; /*Just to be sure (Not possible if there is at least one object in the group)*/
153 
154     /*Focus on the next object*/
155     if(*g->obj_focus == obj) {
156         if(g->frozen) g->frozen = 0;
157 
158         /*If this is the only object in the group then focus to nothing.*/
159         if(_lv_ll_get_head(&g->obj_ll) == g->obj_focus && _lv_ll_get_tail(&g->obj_ll) == g->obj_focus) {
160             (*g->obj_focus)->signal_cb(*g->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
161         }
162         /*If there more objects in the group then focus to the next/prev object*/
163         else {
164             lv_group_refocus(g);
165         }
166     }
167 
168     /* If the focuses object is still the same then it was the only object in the group but it will
169      * be deleted. Set the `obj_focus` to NULL to get back to the initial state of the group with
170      * zero objects*/
171     if(*g->obj_focus == obj) {
172         g->obj_focus = NULL;
173     }
174 
175     /*Search the object and remove it from its group */
176     lv_obj_t ** i;
177     _LV_LL_READ(g->obj_ll, i) {
178         if(*i == obj) {
179             _lv_ll_remove(&g->obj_ll, i);
180             lv_mem_free(i);
181             obj->group_p = NULL;
182             break;
183         }
184     }
185 }
186 
187 /**
188  * Remove all objects from a group
189  * @param group pointer to a group
190  */
lv_group_remove_all_objs(lv_group_t * group)191 void lv_group_remove_all_objs(lv_group_t * group)
192 {
193     /*Defocus the the currently focused object*/
194     if(group->obj_focus != NULL) {
195         (*group->obj_focus)->signal_cb(*group->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
196         lv_obj_invalidate(*group->obj_focus);
197         group->obj_focus = NULL;
198     }
199 
200     /*Remove the objects from the group*/
201     lv_obj_t ** obj;
202     _LV_LL_READ(group->obj_ll, obj) {
203         (*obj)->group_p = NULL;
204     }
205 
206     _lv_ll_clear(&(group->obj_ll));
207 }
208 
209 /**
210  * Focus on an object (defocus the current)
211  * @param obj pointer to an object to focus on
212  */
lv_group_focus_obj(lv_obj_t * obj)213 void lv_group_focus_obj(lv_obj_t * obj)
214 {
215     if(obj == NULL) return;
216     lv_group_t * g = obj->group_p;
217     if(g == NULL) return;
218 
219     if(g->frozen != 0) return;
220 
221     if(g->obj_focus != NULL && obj == *g->obj_focus) return;
222 
223     /*On defocus edit mode must be leaved*/
224     lv_group_set_editing(g, false);
225 
226     lv_obj_t ** i;
227     _LV_LL_READ(g->obj_ll, i) {
228         if(*i == obj) {
229             if(g->obj_focus != NULL) {
230                 (*g->obj_focus)->signal_cb(*g->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
231                 lv_res_t res = lv_event_send(*g->obj_focus, LV_EVENT_DEFOCUSED, NULL);
232                 if(res != LV_RES_OK) return;
233                 lv_obj_invalidate(*g->obj_focus);
234             }
235 
236             g->obj_focus = i;
237 
238             if(g->obj_focus != NULL) {
239                 (*g->obj_focus)->signal_cb(*g->obj_focus, LV_SIGNAL_FOCUS, NULL);
240                 if(g->focus_cb) g->focus_cb(g);
241                 lv_res_t res = lv_event_send(*g->obj_focus, LV_EVENT_FOCUSED, NULL);
242                 if(res != LV_RES_OK) return;
243                 lv_obj_invalidate(*g->obj_focus);
244 
245                 /*If the object or its parent has `top == true` bring it to the foreground*/
246                 obj_to_foreground(*g->obj_focus);
247             }
248             break;
249         }
250     }
251 }
252 
253 /**
254  * Focus the next object in a group (defocus the current)
255  * @param group pointer to a group
256  */
lv_group_focus_next(lv_group_t * group)257 void lv_group_focus_next(lv_group_t * group)
258 {
259     focus_next_core(group, _lv_ll_get_head, _lv_ll_get_next);
260 }
261 
262 /**
263  * Focus the previous object in a group (defocus the current)
264  * @param group pointer to a group
265  */
lv_group_focus_prev(lv_group_t * group)266 void lv_group_focus_prev(lv_group_t * group)
267 {
268     focus_next_core(group, _lv_ll_get_tail, _lv_ll_get_prev);
269 }
270 
271 /**
272  * Do not let to change the focus from the current object
273  * @param group pointer to a group
274  * @param en true: freeze, false: release freezing (normal mode)
275  */
lv_group_focus_freeze(lv_group_t * group,bool en)276 void lv_group_focus_freeze(lv_group_t * group, bool en)
277 {
278     if(en == false)
279         group->frozen = 0;
280     else
281         group->frozen = 1;
282 }
283 
284 /**
285  * Send a control character to the focuses object of a group
286  * @param group pointer to a group
287  * @param c a character (use LV_KEY_.. to navigate)
288  * @return result of focused object in group.
289  */
lv_group_send_data(lv_group_t * group,uint32_t c)290 lv_res_t lv_group_send_data(lv_group_t * group, uint32_t c)
291 {
292     lv_obj_t * act = lv_group_get_focused(group);
293     if(act == NULL) return LV_RES_OK;
294 
295     lv_res_t res;
296 
297     res = act->signal_cb(act, LV_SIGNAL_CONTROL, &c);
298     if(res != LV_RES_OK) return res;
299 
300     res = lv_event_send(act, LV_EVENT_KEY, &c);
301     if(res != LV_RES_OK) return res;
302 
303     return res;
304 }
305 
306 /**
307  * Set a function for a group which will be called when a new object is focused
308  * @param group pointer to a group
309  * @param focus_cb the call back function or NULL if unused
310  */
lv_group_set_focus_cb(lv_group_t * group,lv_group_focus_cb_t focus_cb)311 void lv_group_set_focus_cb(lv_group_t * group, lv_group_focus_cb_t focus_cb)
312 {
313     group->focus_cb = focus_cb;
314 }
315 
316 /**
317  * Manually set the current mode (edit or navigate).
318  * @param group pointer to group
319  * @param edit: true: edit mode; false: navigate mode
320  */
lv_group_set_editing(lv_group_t * group,bool edit)321 void lv_group_set_editing(lv_group_t * group, bool edit)
322 {
323     if(group == NULL) return;
324     uint8_t en_val = edit ? 1 : 0;
325 
326     if(en_val == group->editing) return; /*Do not set the same mode again*/
327 
328     group->editing     = en_val;
329     lv_obj_t * focused = lv_group_get_focused(group);
330 
331     if(focused) {
332         focused->signal_cb(focused, LV_SIGNAL_FOCUS, NULL); /*Focus again to properly leave/open edit/navigate mode*/
333         lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_FOCUSED, NULL);
334         if(res != LV_RES_OK) return;
335 
336         lv_obj_invalidate(focused);
337     }
338 }
339 
340 /**
341  * Set the `click_focus` attribute. If enabled then the object will be focused then it is clicked.
342  * @param group pointer to group
343  * @param en: true: enable `click_focus`
344  */
lv_group_set_click_focus(lv_group_t * group,bool en)345 void lv_group_set_click_focus(lv_group_t * group, bool en)
346 {
347     group->click_focus = en ? 1 : 0;
348 }
349 
lv_group_set_refocus_policy(lv_group_t * group,lv_group_refocus_policy_t policy)350 void lv_group_set_refocus_policy(lv_group_t * group, lv_group_refocus_policy_t policy)
351 {
352     group->refocus_policy = policy & 0x01;
353 }
354 
355 /**
356  * Set whether focus next/prev will allow wrapping from first->last or last->first.
357  * @param group pointer to group
358  * @param en: true: enable `wrap`
359  */
lv_group_set_wrap(lv_group_t * group,bool en)360 void lv_group_set_wrap(lv_group_t * group, bool en)
361 {
362     group->wrap = en ? 1 : 0;
363 }
364 
365 /**
366  * Get the focused object or NULL if there isn't one
367  * @param group pointer to a group
368  * @return pointer to the focused object
369  */
lv_group_get_focused(const lv_group_t * group)370 lv_obj_t * lv_group_get_focused(const lv_group_t * group)
371 {
372     if(!group) return NULL;
373     if(group->obj_focus == NULL) return NULL;
374 
375     return *group->obj_focus;
376 }
377 
378 #if LV_USE_USER_DATA
379 /**
380  * Get a pointer to the group's user data
381  * @param group pointer to an group
382  * @return pointer to the user data
383  */
lv_group_get_user_data(lv_group_t * group)384 lv_group_user_data_t * lv_group_get_user_data(lv_group_t * group)
385 {
386     return &group->user_data;
387 }
388 #endif
389 
390 /**
391  * Get the focus callback function of a group
392  * @param group pointer to a group
393  * @return the call back function or NULL if not set
394  */
lv_group_get_focus_cb(const lv_group_t * group)395 lv_group_focus_cb_t lv_group_get_focus_cb(const lv_group_t * group)
396 {
397     if(!group) return NULL;
398     return group->focus_cb;
399 }
400 
401 /**
402  * Get the current mode (edit or navigate).
403  * @param group pointer to group
404  * @return true: edit mode; false: navigate mode
405  */
lv_group_get_editing(const lv_group_t * group)406 bool lv_group_get_editing(const lv_group_t * group)
407 {
408     if(!group) return false;
409     return group->editing ? true : false;
410 }
411 
412 /**
413  * Get the `click_focus` attribute.
414  * @param group pointer to group
415  * @return true: `click_focus` is enabled; false: disabled
416  */
lv_group_get_click_focus(const lv_group_t * group)417 bool lv_group_get_click_focus(const lv_group_t * group)
418 {
419     if(!group) return false;
420     return group->click_focus ? true : false;
421 }
422 
423 /**
424  * Get whether focus next/prev will allow wrapping from first->last or last->first object.
425  * @param group pointer to group
426  * @param en: true: wrapping enabled; false: wrapping disabled
427  */
lv_group_get_wrap(lv_group_t * group)428 bool lv_group_get_wrap(lv_group_t * group)
429 {
430     if(!group) return false;
431     return group->wrap ? true : false;
432 }
433 
434 /**********************
435  *   STATIC FUNCTIONS
436  **********************/
437 
lv_group_refocus(lv_group_t * g)438 static void lv_group_refocus(lv_group_t * g)
439 {
440     /*Refocus must temporarily allow wrapping to work correctly*/
441     uint8_t temp_wrap = g->wrap;
442     g->wrap           = 1;
443 
444     if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_NEXT)
445         lv_group_focus_next(g);
446     else if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_PREV)
447         lv_group_focus_prev(g);
448     /*Restore wrap property*/
449     g->wrap = temp_wrap;
450 }
451 
focus_next_core(lv_group_t * group,void * (* begin)(const lv_ll_t *),void * (* move)(const lv_ll_t *,const void *))452 static void focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *),
453                             void * (*move)(const lv_ll_t *, const void *))
454 {
455     if(group->frozen) return;
456 
457     lv_obj_t ** obj_next     = group->obj_focus;
458     lv_obj_t ** obj_sentinel = NULL;
459     bool can_move            = true;
460     bool can_begin           = true;
461 
462     for(;;) {
463         if(obj_next == NULL) {
464             if(group->wrap || obj_sentinel == NULL) {
465                 if(!can_begin) return;
466                 obj_next  = begin(&group->obj_ll);
467                 can_move  = false;
468                 can_begin = false;
469             }
470             else {
471                 /*Currently focused object is the last/first in the group, keep it that way*/
472                 return;
473             }
474         }
475 
476         if(obj_sentinel == NULL) {
477             obj_sentinel = obj_next;
478             if(obj_sentinel == NULL) return; /*Group is empty*/
479         }
480 
481         if(can_move) {
482             obj_next = move(&group->obj_ll, obj_next);
483 
484             /*Give up if we walked the entire list and haven't found another visible object*/
485             if(obj_next == obj_sentinel) return;
486         }
487 
488         can_move = true;
489 
490         if(obj_next == NULL) continue;
491 
492         /*Hidden objects don't receive focus*/
493         if(!lv_obj_get_hidden(*obj_next)) break;
494     }
495 
496     if(obj_next == group->obj_focus) return; /*There's only one visible object and it's already focused*/
497 
498     if(group->obj_focus) {
499         (*group->obj_focus)->signal_cb(*group->obj_focus, LV_SIGNAL_DEFOCUS, NULL);
500         lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_DEFOCUSED, NULL);
501         if(res != LV_RES_OK) return;
502         lv_obj_invalidate(*group->obj_focus);
503     }
504 
505     group->obj_focus = obj_next;
506 
507     (*group->obj_focus)->signal_cb(*group->obj_focus, LV_SIGNAL_FOCUS, NULL);
508     lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_FOCUSED, NULL);
509     if(res != LV_RES_OK) return;
510 
511     /*If the object or its parent has `top == true` bring it to the foreground*/
512     obj_to_foreground(*group->obj_focus);
513 
514     lv_obj_invalidate(*group->obj_focus);
515 
516     if(group->focus_cb) group->focus_cb(group);
517 }
518 
obj_to_foreground(lv_obj_t * obj)519 static void obj_to_foreground(lv_obj_t * obj)
520 {
521     /*Search for 'top' attribute*/
522     lv_obj_t * i        = obj;
523     lv_obj_t * last_top = NULL;
524     while(i != NULL) {
525         if(i->top != 0) last_top = i;
526         i = lv_obj_get_parent(i);
527     }
528 
529     if(last_top != NULL) {
530         /*Move the last_top object to the foreground*/
531         lv_obj_move_foreground(last_top);
532     }
533 }
534 
535 #endif /*LV_USE_GROUP != 0*/
536