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