/** * @file lv_group.c * */ /********************* * INCLUDES *********************/ #include #include "lv_group.h" #include "../misc/lv_gc.h" #include "../core/lv_obj.h" #include "../core/lv_indev.h" /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ static bool focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *), void * (*move)(const lv_ll_t *, const void *)); static void lv_group_refocus(lv_group_t * g); static lv_indev_t * get_indev(const lv_group_t * g); /********************** * STATIC VARIABLES **********************/ static lv_group_t * default_group; /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void _lv_group_init(void) { _lv_ll_init(&LV_GC_ROOT(_lv_group_ll), sizeof(lv_group_t)); } lv_group_t * lv_group_create(void) { lv_group_t * group = _lv_ll_ins_head(&LV_GC_ROOT(_lv_group_ll)); LV_ASSERT_MALLOC(group); if(group == NULL) return NULL; _lv_ll_init(&group->obj_ll, sizeof(lv_obj_t *)); group->obj_focus = NULL; group->frozen = 0; group->focus_cb = NULL; group->edge_cb = NULL; group->editing = 0; group->refocus_policy = LV_GROUP_REFOCUS_POLICY_PREV; group->wrap = 1; #if LV_USE_USER_DATA group->user_data = NULL; #endif return group; } void lv_group_del(lv_group_t * group) { /*Defocus the currently focused object*/ if(group->obj_focus != NULL) { lv_event_send(*group->obj_focus, LV_EVENT_DEFOCUSED, get_indev(group)); lv_obj_invalidate(*group->obj_focus); } /*Remove the objects from the group*/ lv_obj_t ** obj; _LV_LL_READ(&group->obj_ll, obj) { if((*obj)->spec_attr)(*obj)->spec_attr->group_p = NULL; } /*Remove the group from any indev devices */ lv_indev_t * indev = lv_indev_get_next(NULL); while(indev) { if(indev->group == group) { lv_indev_set_group(indev, NULL); } indev = lv_indev_get_next(indev); } if(default_group == group) default_group = NULL; _lv_ll_clear(&(group->obj_ll)); _lv_ll_remove(&LV_GC_ROOT(_lv_group_ll), group); lv_mem_free(group); } void lv_group_set_default(lv_group_t * group) { default_group = group; } lv_group_t * lv_group_get_default(void) { return default_group; } void lv_group_add_obj(lv_group_t * group, lv_obj_t * obj) { if(group == NULL) return; LV_LOG_TRACE("begin"); /*Be sure the object is removed from its current group*/ lv_group_remove_obj(obj); /*Do not add the object twice*/ lv_obj_t ** obj_i; _LV_LL_READ(&group->obj_ll, obj_i) { if((*obj_i) == obj) { LV_LOG_INFO("the object is already added to this group"); return; } } /*If the object is already in a group and focused then refocus it*/ lv_group_t * group_cur = lv_obj_get_group(obj); if(group_cur) { if(obj->spec_attr->group_p && *(obj->spec_attr->group_p->obj_focus) == obj) { lv_group_refocus(group_cur); LV_LOG_INFO("changing object's group"); } } if(obj->spec_attr == NULL) lv_obj_allocate_spec_attr(obj); obj->spec_attr->group_p = group; lv_obj_t ** next = _lv_ll_ins_tail(&group->obj_ll); LV_ASSERT_MALLOC(next); if(next == NULL) return; *next = obj; /*If the head and the tail is equal then there is only one object in the linked list. *In this case automatically activate it*/ if(_lv_ll_get_head(&group->obj_ll) == next) { lv_group_refocus(group); } LV_LOG_TRACE("finished"); } void lv_group_swap_obj(lv_obj_t * obj1, lv_obj_t * obj2) { lv_group_t * g1 = lv_obj_get_group(obj1); lv_group_t * g2 = lv_obj_get_group(obj2); if(g1 != g2) return; if(g1 == NULL) return; /*Do not add the object twice*/ lv_obj_t ** obj_i; _LV_LL_READ(&g1->obj_ll, obj_i) { if((*obj_i) == obj1)(*obj_i) = obj2; else if((*obj_i) == obj2)(*obj_i) = obj1; } if(*g1->obj_focus == obj1) lv_group_focus_obj(obj2); else if(*g1->obj_focus == obj2) lv_group_focus_obj(obj1); } void lv_group_remove_obj(lv_obj_t * obj) { lv_group_t * g = lv_obj_get_group(obj); if(g == NULL) return; LV_LOG_TRACE("begin"); /*Focus on the next object*/ if(g->obj_focus && *g->obj_focus == obj) { if(g->frozen) g->frozen = 0; /*If this is the only object in the group then focus to nothing.*/ if(_lv_ll_get_head(&g->obj_ll) == g->obj_focus && _lv_ll_get_tail(&g->obj_ll) == g->obj_focus) { lv_event_send(*g->obj_focus, LV_EVENT_DEFOCUSED, get_indev(g)); } /*If there more objects in the group then focus to the next/prev object*/ else { lv_group_refocus(g); } } /*If the focuses object is still the same then it was the only object in the group but it will *be deleted. Set the `obj_focus` to NULL to get back to the initial state of the group with *zero objects*/ if(g->obj_focus && *g->obj_focus == obj) { g->obj_focus = NULL; } /*Search the object and remove it from its group*/ lv_obj_t ** i; _LV_LL_READ(&g->obj_ll, i) { if(*i == obj) { _lv_ll_remove(&g->obj_ll, i); lv_mem_free(i); if(obj->spec_attr) obj->spec_attr->group_p = NULL; break; } } LV_LOG_TRACE("finished"); } void lv_group_remove_all_objs(lv_group_t * group) { /*Defocus the currently focused object*/ if(group->obj_focus != NULL) { lv_event_send(*group->obj_focus, LV_EVENT_DEFOCUSED, get_indev(group)); lv_obj_invalidate(*group->obj_focus); group->obj_focus = NULL; } /*Remove the objects from the group*/ lv_obj_t ** obj; _LV_LL_READ(&group->obj_ll, obj) { if((*obj)->spec_attr)(*obj)->spec_attr->group_p = NULL; } _lv_ll_clear(&(group->obj_ll)); } void lv_group_focus_obj(lv_obj_t * obj) { if(obj == NULL) return; lv_group_t * g = lv_obj_get_group(obj); if(g == NULL) return; if(g->frozen != 0) return; /*On defocus edit mode must be leaved*/ lv_group_set_editing(g, false); lv_obj_t ** i; _LV_LL_READ(&g->obj_ll, i) { if(*i == obj) { if(g->obj_focus != NULL && obj != *g->obj_focus) { /*Do not defocus if the same object needs to be focused again*/ lv_res_t res = lv_event_send(*g->obj_focus, LV_EVENT_DEFOCUSED, get_indev(g)); if(res != LV_RES_OK) return; lv_obj_invalidate(*g->obj_focus); } g->obj_focus = i; if(g->obj_focus != NULL) { if(g->focus_cb) g->focus_cb(g); lv_res_t res = lv_event_send(*g->obj_focus, LV_EVENT_FOCUSED, get_indev(g)); if(res != LV_RES_OK) return; lv_obj_invalidate(*g->obj_focus); } break; } } } void lv_group_focus_next(lv_group_t * group) { bool focus_changed = focus_next_core(group, _lv_ll_get_head, _lv_ll_get_next); if(group->edge_cb) { if(!focus_changed) group->edge_cb(group, true); } } void lv_group_focus_prev(lv_group_t * group) { bool focus_changed = focus_next_core(group, _lv_ll_get_tail, _lv_ll_get_prev); if(group->edge_cb) { if(!focus_changed) group->edge_cb(group, false); } } void lv_group_focus_freeze(lv_group_t * group, bool en) { if(en == false) group->frozen = 0; else group->frozen = 1; } lv_res_t lv_group_send_data(lv_group_t * group, uint32_t c) { lv_obj_t * act = lv_group_get_focused(group); if(act == NULL) return LV_RES_OK; if(lv_obj_has_state(act, LV_STATE_DISABLED)) return LV_RES_OK; return lv_event_send(act, LV_EVENT_KEY, &c); } void lv_group_set_focus_cb(lv_group_t * group, lv_group_focus_cb_t focus_cb) { group->focus_cb = focus_cb; } void lv_group_set_edge_cb(lv_group_t * group, lv_group_edge_cb_t edge_cb) { group->edge_cb = edge_cb; } void lv_group_set_editing(lv_group_t * group, bool edit) { if(group == NULL) return; uint8_t en_val = edit ? 1 : 0; if(en_val == group->editing) return; /*Do not set the same mode again*/ group->editing = en_val; lv_obj_t * focused = lv_group_get_focused(group); if(focused) { lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_FOCUSED, get_indev(group)); if(res != LV_RES_OK) return; lv_obj_invalidate(focused); } } void lv_group_set_refocus_policy(lv_group_t * group, lv_group_refocus_policy_t policy) { group->refocus_policy = policy & 0x01; } void lv_group_set_wrap(lv_group_t * group, bool en) { group->wrap = en ? 1 : 0; } lv_obj_t * lv_group_get_focused(const lv_group_t * group) { if(!group) return NULL; if(group->obj_focus == NULL) return NULL; return *group->obj_focus; } lv_group_focus_cb_t lv_group_get_focus_cb(const lv_group_t * group) { if(!group) return NULL; return group->focus_cb; } lv_group_edge_cb_t lv_group_get_edge_cb(const lv_group_t * group) { if(!group) return NULL; return group->edge_cb; } bool lv_group_get_editing(const lv_group_t * group) { if(!group) return false; return group->editing ? true : false; } bool lv_group_get_wrap(lv_group_t * group) { if(!group) return false; return group->wrap ? true : false; } uint32_t lv_group_get_obj_count(lv_group_t * group) { return _lv_ll_get_len(&group->obj_ll); } /********************** * STATIC FUNCTIONS **********************/ static void lv_group_refocus(lv_group_t * g) { /*Refocus must temporarily allow wrapping to work correctly*/ uint8_t temp_wrap = g->wrap; g->wrap = 1; if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_NEXT) lv_group_focus_next(g); else if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_PREV) lv_group_focus_prev(g); /*Restore wrap property*/ g->wrap = temp_wrap; } static bool focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *), void * (*move)(const lv_ll_t *, const void *)) { bool focus_changed = false; if(group->frozen) return focus_changed; lv_obj_t ** obj_next = group->obj_focus; lv_obj_t ** obj_sentinel = NULL; bool can_move = true; bool can_begin = true; for(;;) { if(obj_next == NULL) { if(group->wrap || obj_sentinel == NULL) { if(!can_begin) return focus_changed; obj_next = begin(&group->obj_ll); can_move = false; can_begin = false; } else { /*Currently focused object is the last/first in the group, keep it that way*/ return focus_changed; } } if(obj_sentinel == NULL) { obj_sentinel = obj_next; if(obj_sentinel == NULL) return focus_changed; /*Group is empty*/ } if(can_move) { obj_next = move(&group->obj_ll, obj_next); /*Give up if we walked the entire list and haven't found another visible object*/ if(obj_next == obj_sentinel) return focus_changed; } can_move = true; if(obj_next == NULL) continue; if(lv_obj_get_state(*obj_next) & LV_STATE_DISABLED) continue; /*Hidden objects don't receive focus. *If any parent is hidden, the object is also hidden)*/ lv_obj_t * parent = *obj_next; while(parent) { if(lv_obj_has_flag(parent, LV_OBJ_FLAG_HIDDEN)) break; parent = lv_obj_get_parent(parent); } if(parent && lv_obj_has_flag(parent, LV_OBJ_FLAG_HIDDEN)) continue; /*If we got her a good candidate is found*/ break; } if(obj_next == group->obj_focus) return focus_changed; /*There's only one visible object and it's already focused*/ if(group->obj_focus) { lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_DEFOCUSED, get_indev(group)); if(res != LV_RES_OK) return focus_changed; lv_obj_invalidate(*group->obj_focus); } group->obj_focus = obj_next; lv_res_t res = lv_event_send(*group->obj_focus, LV_EVENT_FOCUSED, get_indev(group)); if(res != LV_RES_OK) return focus_changed; lv_obj_invalidate(*group->obj_focus); if(group->focus_cb) group->focus_cb(group); focus_changed = true; return focus_changed; } /** * Find an indev preferably with KEYPAD or ENCOEDR type that uses the given group. * In other words, find an indev, that is related to the given group. * In the worst case simply return the latest indev * @param g a group the find in the indevs * @return the suggested indev */ static lv_indev_t * get_indev(const lv_group_t * g) { lv_indev_t * indev_encoder = NULL; lv_indev_t * indev_group = NULL; lv_indev_t * indev = lv_indev_get_next(NULL); while(indev) { lv_indev_type_t indev_type = lv_indev_get_type(indev); if(indev->group == g) { /*Prefer KEYPAD*/ if(indev_type == LV_INDEV_TYPE_KEYPAD) return indev; if(indev_type == LV_INDEV_TYPE_ENCODER) indev_encoder = indev; indev_group = indev; } indev = lv_indev_get_next(indev); } if(indev_encoder) return indev_encoder; if(indev_group) return indev_group; /*In lack of a better option use the first input device. (It can be NULL if there is no input device)*/ return lv_indev_get_next(NULL); }