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