1 /**
2  * @file lv_group.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include <stddef.h>
10 
11 #include "lv_group.h"
12 #include "../misc/lv_gc.h"
13 #include "../core/lv_obj.h"
14 #include "../core/lv_indev.h"
15 
16 /*********************
17  *      DEFINES
18  *********************/
19 
20 /**********************
21  *      TYPEDEFS
22  **********************/
23 
24 /**********************
25  *  STATIC PROTOTYPES
26  **********************/
27 static bool focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *),
28                             void * (*move)(const lv_ll_t *, const void *));
29 static void lv_group_refocus(lv_group_t * g);
30 static lv_indev_t * get_indev(const lv_group_t * g);
31 
32 /**********************
33  *  STATIC VARIABLES
34  **********************/
35 static lv_group_t * default_group;
36 
37 /**********************
38  *      MACROS
39  **********************/
40 
41 /**********************
42  *   GLOBAL FUNCTIONS
43  **********************/
44 
_lv_group_init(void)45 void _lv_group_init(void)
46 {
47     _lv_ll_init(&LV_GC_ROOT(_lv_group_ll), sizeof(lv_group_t));
48 }
49 
lv_group_create(void)50 lv_group_t * lv_group_create(void)
51 {
52     lv_group_t * group = _lv_ll_ins_head(&LV_GC_ROOT(_lv_group_ll));
53     LV_ASSERT_MALLOC(group);
54     if(group == NULL) return NULL;
55     _lv_ll_init(&group->obj_ll, sizeof(lv_obj_t *));
56 
57     group->obj_focus      = NULL;
58     group->frozen         = 0;
59     group->focus_cb       = NULL;
60     group->edge_cb        = NULL;
61     group->editing        = 0;
62     group->refocus_policy = LV_GROUP_REFOCUS_POLICY_PREV;
63     group->wrap           = 1;
64 
65 #if LV_USE_USER_DATA
66     group->user_data = NULL;
67 #endif
68 
69     return group;
70 }
71 
lv_group_del(lv_group_t * group)72 void lv_group_del(lv_group_t * group)
73 {
74     /*Defocus the currently focused object*/
75     if(group->obj_focus != NULL) {
76         lv_event_send(*group->obj_focus, LV_EVENT_DEFOCUSED, get_indev(group));
77         lv_obj_invalidate(*group->obj_focus);
78     }
79 
80     /*Remove the objects from the group*/
81     lv_obj_t ** obj;
82     _LV_LL_READ(&group->obj_ll, obj) {
83         if((*obj)->spec_attr)(*obj)->spec_attr->group_p = NULL;
84     }
85 
86     /*Remove the group from any indev devices */
87     lv_indev_t * indev = lv_indev_get_next(NULL);
88     while(indev) {
89         if(indev->group == group) {
90             lv_indev_set_group(indev, NULL);
91         }
92         indev = lv_indev_get_next(indev);
93     }
94 
95     if(default_group == group) default_group = NULL;
96     _lv_ll_clear(&(group->obj_ll));
97     _lv_ll_remove(&LV_GC_ROOT(_lv_group_ll), group);
98     lv_mem_free(group);
99 }
100 
lv_group_set_default(lv_group_t * group)101 void lv_group_set_default(lv_group_t * group)
102 {
103     default_group = group;
104 }
105 
lv_group_get_default(void)106 lv_group_t * lv_group_get_default(void)
107 {
108     return default_group;
109 }
110 
lv_group_add_obj(lv_group_t * group,lv_obj_t * obj)111 void lv_group_add_obj(lv_group_t * group, lv_obj_t * obj)
112 {
113     if(group == NULL) return;
114 
115     LV_LOG_TRACE("begin");
116 
117     /*Be sure the object is removed from its current group*/
118     lv_group_remove_obj(obj);
119 
120     /*Do not add the object twice*/
121     lv_obj_t ** obj_i;
122     _LV_LL_READ(&group->obj_ll, obj_i) {
123         if((*obj_i) == obj) {
124             LV_LOG_INFO("the object is already added to this group");
125             return;
126         }
127     }
128 
129     /*If the object is already in a group and focused then refocus it*/
130     lv_group_t * group_cur = lv_obj_get_group(obj);
131     if(group_cur) {
132         if(obj->spec_attr->group_p && *(obj->spec_attr->group_p->obj_focus) == obj) {
133             lv_group_refocus(group_cur);
134 
135             LV_LOG_INFO("changing object's group");
136         }
137     }
138 
139     if(obj->spec_attr == NULL) lv_obj_allocate_spec_attr(obj);
140     obj->spec_attr->group_p = group;
141 
142     lv_obj_t ** next = _lv_ll_ins_tail(&group->obj_ll);
143     LV_ASSERT_MALLOC(next);
144     if(next == NULL) return;
145     *next = obj;
146 
147     /*If the head and the tail is equal then there is only one object in the linked list.
148      *In this case automatically activate it*/
149     if(_lv_ll_get_head(&group->obj_ll) == next) {
150         lv_group_refocus(group);
151     }
152 
153     LV_LOG_TRACE("finished");
154 }
155 
lv_group_swap_obj(lv_obj_t * obj1,lv_obj_t * obj2)156 void lv_group_swap_obj(lv_obj_t * obj1, lv_obj_t * obj2)
157 {
158     lv_group_t * g1 = lv_obj_get_group(obj1);
159     lv_group_t * g2 = lv_obj_get_group(obj2);
160     if(g1 != g2) return;
161     if(g1 == NULL) return;
162 
163     /*Do not add the object twice*/
164     lv_obj_t ** obj_i;
165     _LV_LL_READ(&g1->obj_ll, obj_i) {
166         if((*obj_i) == obj1)(*obj_i) = obj2;
167         else if((*obj_i) == obj2)(*obj_i) = obj1;
168     }
169 
170     if(*g1->obj_focus == obj1) lv_group_focus_obj(obj2);
171     else if(*g1->obj_focus == obj2) lv_group_focus_obj(obj1);
172 
173 }
174 
lv_group_remove_obj(lv_obj_t * obj)175 void lv_group_remove_obj(lv_obj_t * obj)
176 {
177     lv_group_t * g = lv_obj_get_group(obj);
178     if(g == NULL) return;
179 
180     LV_LOG_TRACE("begin");
181 
182     /*Focus on the next object*/
183     if(g->obj_focus && *g->obj_focus == obj) {
184         if(g->frozen) g->frozen = 0;
185 
186         /*If this is the only object in the group then focus to nothing.*/
187         if(_lv_ll_get_head(&g->obj_ll) == g->obj_focus && _lv_ll_get_tail(&g->obj_ll) == g->obj_focus) {
188             lv_event_send(*g->obj_focus, LV_EVENT_DEFOCUSED, get_indev(g));
189         }
190         /*If there more objects in the group then focus to the next/prev object*/
191         else {
192             lv_group_refocus(g);
193         }
194     }
195 
196     /*If the focuses object is still the same then it was the only object in the group but it will
197      *be deleted. Set the `obj_focus` to NULL to get back to the initial state of the group with
198      *zero objects*/
199     if(g->obj_focus && *g->obj_focus == obj) {
200         g->obj_focus = NULL;
201     }
202 
203     /*Search the object and remove it from its group*/
204     lv_obj_t ** i;
205     _LV_LL_READ(&g->obj_ll, i) {
206         if(*i == obj) {
207             _lv_ll_remove(&g->obj_ll, i);
208             lv_mem_free(i);
209             if(obj->spec_attr) obj->spec_attr->group_p = NULL;
210             break;
211         }
212     }
213     LV_LOG_TRACE("finished");
214 }
215 
lv_group_remove_all_objs(lv_group_t * group)216 void lv_group_remove_all_objs(lv_group_t * group)
217 {
218     /*Defocus the currently focused object*/
219     if(group->obj_focus != NULL) {
220         lv_event_send(*group->obj_focus, LV_EVENT_DEFOCUSED, get_indev(group));
221         lv_obj_invalidate(*group->obj_focus);
222         group->obj_focus = NULL;
223     }
224 
225     /*Remove the objects from the group*/
226     lv_obj_t ** obj;
227     _LV_LL_READ(&group->obj_ll, obj) {
228         if((*obj)->spec_attr)(*obj)->spec_attr->group_p = NULL;
229     }
230 
231     _lv_ll_clear(&(group->obj_ll));
232 }
233 
lv_group_focus_obj(lv_obj_t * obj)234 void lv_group_focus_obj(lv_obj_t * obj)
235 {
236     if(obj == NULL) return;
237     lv_group_t * g = lv_obj_get_group(obj);
238     if(g == NULL) return;
239 
240     if(g->frozen != 0) return;
241 
242     /*On defocus edit mode must be leaved*/
243     lv_group_set_editing(g, false);
244 
245     lv_obj_t ** i;
246     _LV_LL_READ(&g->obj_ll, i) {
247         if(*i == obj) {
248             if(g->obj_focus != NULL && obj != *g->obj_focus) {  /*Do not defocus if the same object needs to be focused again*/
249                 lv_res_t res = lv_event_send(*g->obj_focus, LV_EVENT_DEFOCUSED, get_indev(g));
250                 if(res != LV_RES_OK) return;
251                 lv_obj_invalidate(*g->obj_focus);
252             }
253 
254             g->obj_focus = i;
255 
256             if(g->obj_focus != NULL) {
257                 if(g->focus_cb) g->focus_cb(g);
258                 lv_res_t res = lv_event_send(*g->obj_focus, LV_EVENT_FOCUSED, get_indev(g));
259                 if(res != LV_RES_OK) return;
260                 lv_obj_invalidate(*g->obj_focus);
261             }
262             break;
263         }
264     }
265 }
266 
lv_group_focus_next(lv_group_t * group)267 void lv_group_focus_next(lv_group_t * group)
268 {
269     bool focus_changed = focus_next_core(group, _lv_ll_get_head, _lv_ll_get_next);
270     if(group->edge_cb) {
271         if(!focus_changed)
272             group->edge_cb(group, true);
273     }
274 }
275 
lv_group_focus_prev(lv_group_t * group)276 void lv_group_focus_prev(lv_group_t * group)
277 {
278     bool focus_changed = focus_next_core(group, _lv_ll_get_tail, _lv_ll_get_prev);
279     if(group->edge_cb) {
280         if(!focus_changed)
281             group->edge_cb(group, false);
282     }
283 }
284 
lv_group_focus_freeze(lv_group_t * group,bool en)285 void lv_group_focus_freeze(lv_group_t * group, bool en)
286 {
287     if(en == false) group->frozen = 0;
288     else group->frozen = 1;
289 }
290 
lv_group_send_data(lv_group_t * group,uint32_t c)291 lv_res_t lv_group_send_data(lv_group_t * group, uint32_t c)
292 {
293     lv_obj_t * act = lv_group_get_focused(group);
294     if(act == NULL) return LV_RES_OK;
295 
296     if(lv_obj_has_state(act, LV_STATE_DISABLED)) return LV_RES_OK;
297 
298     return lv_event_send(act, LV_EVENT_KEY, &c);
299 }
300 
lv_group_set_focus_cb(lv_group_t * group,lv_group_focus_cb_t focus_cb)301 void lv_group_set_focus_cb(lv_group_t * group, lv_group_focus_cb_t focus_cb)
302 {
303     group->focus_cb = focus_cb;
304 }
305 
lv_group_set_edge_cb(lv_group_t * group,lv_group_edge_cb_t edge_cb)306 void lv_group_set_edge_cb(lv_group_t * group, lv_group_edge_cb_t edge_cb)
307 {
308     group->edge_cb = edge_cb;
309 }
310 
lv_group_set_editing(lv_group_t * group,bool edit)311 void lv_group_set_editing(lv_group_t * group, bool edit)
312 {
313     if(group == NULL) return;
314     uint8_t en_val = edit ? 1 : 0;
315 
316     if(en_val == group->editing) return; /*Do not set the same mode again*/
317 
318     group->editing     = en_val;
319     lv_obj_t * focused = lv_group_get_focused(group);
320 
321     if(focused) {
322         lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_FOCUSED, get_indev(group));
323         if(res != LV_RES_OK) return;
324 
325         lv_obj_invalidate(focused);
326     }
327 }
328 
lv_group_set_refocus_policy(lv_group_t * group,lv_group_refocus_policy_t policy)329 void lv_group_set_refocus_policy(lv_group_t * group, lv_group_refocus_policy_t policy)
330 {
331     group->refocus_policy = policy & 0x01;
332 }
333 
lv_group_set_wrap(lv_group_t * group,bool en)334 void lv_group_set_wrap(lv_group_t * group, bool en)
335 {
336     group->wrap = en ? 1 : 0;
337 }
338 
lv_group_get_focused(const lv_group_t * group)339 lv_obj_t * lv_group_get_focused(const lv_group_t * group)
340 {
341     if(!group) return NULL;
342     if(group->obj_focus == NULL) return NULL;
343 
344     return *group->obj_focus;
345 }
346 
lv_group_get_focus_cb(const lv_group_t * group)347 lv_group_focus_cb_t lv_group_get_focus_cb(const lv_group_t * group)
348 {
349     if(!group) return NULL;
350     return group->focus_cb;
351 }
352 
lv_group_get_edge_cb(const lv_group_t * group)353 lv_group_edge_cb_t lv_group_get_edge_cb(const lv_group_t * group)
354 {
355     if(!group) return NULL;
356     return group->edge_cb;
357 }
358 
lv_group_get_editing(const lv_group_t * group)359 bool lv_group_get_editing(const lv_group_t * group)
360 {
361     if(!group) return false;
362     return group->editing ? true : false;
363 }
364 
lv_group_get_wrap(lv_group_t * group)365 bool lv_group_get_wrap(lv_group_t * group)
366 {
367     if(!group) return false;
368     return group->wrap ? true : false;
369 }
370 
lv_group_get_obj_count(lv_group_t * group)371 uint32_t lv_group_get_obj_count(lv_group_t * group)
372 {
373     return _lv_ll_get_len(&group->obj_ll);
374 }
375 /**********************
376  *   STATIC FUNCTIONS
377  **********************/
378 
lv_group_refocus(lv_group_t * g)379 static void lv_group_refocus(lv_group_t * g)
380 {
381     /*Refocus must temporarily allow wrapping to work correctly*/
382     uint8_t temp_wrap = g->wrap;
383     g->wrap           = 1;
384 
385     if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_NEXT)
386         lv_group_focus_next(g);
387     else if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_PREV)
388         lv_group_focus_prev(g);
389     /*Restore wrap property*/
390     g->wrap = temp_wrap;
391 }
392 
focus_next_core(lv_group_t * group,void * (* begin)(const lv_ll_t *),void * (* move)(const lv_ll_t *,const void *))393 static bool focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *),
394                             void * (*move)(const lv_ll_t *, const void *))
395 {
396     bool focus_changed = false;
397     if(group->frozen) return focus_changed;
398 
399     lv_obj_t ** obj_next     = group->obj_focus;
400     lv_obj_t ** obj_sentinel = NULL;
401     bool can_move            = true;
402     bool can_begin           = true;
403 
404     for(;;) {
405         if(obj_next == NULL) {
406             if(group->wrap || obj_sentinel == NULL) {
407                 if(!can_begin) return focus_changed;
408                 obj_next  = begin(&group->obj_ll);
409                 can_move  = false;
410                 can_begin = false;
411             }
412             else {
413                 /*Currently focused object is the last/first in the group, keep it that way*/
414                 return focus_changed;
415             }
416         }
417 
418         if(obj_sentinel == NULL) {
419             obj_sentinel = obj_next;
420             if(obj_sentinel == NULL) return focus_changed; /*Group is empty*/
421         }
422 
423         if(can_move) {
424             obj_next = move(&group->obj_ll, obj_next);
425 
426             /*Give up if we walked the entire list and haven't found another visible object*/
427             if(obj_next == obj_sentinel) return focus_changed;
428         }
429 
430         can_move = true;
431 
432         if(obj_next == NULL) continue;
433         if(lv_obj_get_state(*obj_next) & LV_STATE_DISABLED) continue;
434 
435         /*Hidden objects don't receive focus.
436          *If any parent is hidden, the object is also hidden)*/
437         lv_obj_t * parent = *obj_next;
438         while(parent) {
439             if(lv_obj_has_flag(parent, LV_OBJ_FLAG_HIDDEN)) break;
440             parent = lv_obj_get_parent(parent);
441         }
442 
443         if(parent && lv_obj_has_flag(parent, LV_OBJ_FLAG_HIDDEN)) continue;
444 
445         /*If we got her a good candidate is found*/
446         break;
447     }
448 
449     if(obj_next == group->obj_focus) return focus_changed; /*There's only one visible object and it's already focused*/
450 
451     if(group->obj_focus) {
452         lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_DEFOCUSED, get_indev(group));
453         if(res != LV_RES_OK) return focus_changed;
454         lv_obj_invalidate(*group->obj_focus);
455     }
456 
457     group->obj_focus = obj_next;
458 
459     lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_FOCUSED, get_indev(group));
460     if(res != LV_RES_OK) return focus_changed;
461 
462     lv_obj_invalidate(*group->obj_focus);
463 
464     if(group->focus_cb) group->focus_cb(group);
465     focus_changed = true;
466     return focus_changed;
467 }
468 
469 /**
470  * Find an indev preferably with KEYPAD or ENCOEDR type that uses the given group.
471  * In other words, find an indev, that is related to the given group.
472  * In the worst case simply return the latest indev
473  * @param g     a group the find in the indevs
474  * @return      the suggested indev
475  */
get_indev(const lv_group_t * g)476 static lv_indev_t * get_indev(const lv_group_t * g)
477 {
478     lv_indev_t * indev_encoder = NULL;
479     lv_indev_t * indev_group = NULL;
480     lv_indev_t * indev = lv_indev_get_next(NULL);
481     while(indev) {
482         lv_indev_type_t indev_type = lv_indev_get_type(indev);
483         if(indev->group == g) {
484             /*Prefer KEYPAD*/
485             if(indev_type == LV_INDEV_TYPE_KEYPAD) return indev;
486             if(indev_type == LV_INDEV_TYPE_ENCODER) indev_encoder = indev;
487             indev_group = indev;
488         }
489         indev = lv_indev_get_next(indev);
490     }
491 
492     if(indev_encoder) return indev_encoder;
493     if(indev_group) return indev_group;
494 
495     /*In lack of a better option use the first input device. (It can be NULL if there is no input device)*/
496     return lv_indev_get_next(NULL);
497 }
498 
499