/** * @file lv_ddlist.c * */ /********************* * INCLUDES *********************/ #include "lv_conf.h" #if USE_LV_DDLIST != 0 #include "lv_ddlist.h" #include "../lv_draw/lv_draw.h" #include "../lv_misc/anim.h" /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ /********************** * STATIC PROTOTYPES **********************/ static bool lv_ddlist_design(lv_obj_t * ddlist, const area_t * mask, lv_design_mode_t mode); static lv_action_res_t lv_ddlist_rel_action(lv_obj_t * ddlist, lv_dispi_t * dispi); static void lv_ddlist_refr_size(lv_obj_t * ddlist, bool anim_en); static void lv_ddlist_pos_act_option(lv_obj_t * ddlist); static void lv_ddlists_init(void); /********************** * STATIC VARIABLES **********************/ static lv_ddlists_t lv_ddlists_def; /*Default drop down list style*/ static lv_design_f_t ancestor_design_f; static const char * def_options[] = {"Option 1", "Option 2", "Option 3", ""}; /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ /*----------------- * Create function *-----------------*/ /** * Create a drop down list objects * @param par pointer to an object, it will be the parent of the new drop down list * @param copy pointer to a drop down list object, if not NULL then the new object will be copied from it * @return pointer to the created drop down list */ lv_obj_t * lv_ddlist_create(lv_obj_t * par, lv_obj_t * copy) { /*Create the ancestor drop down list*/ lv_obj_t * new_ddlist = lv_page_create(par, copy); dm_assert(new_ddlist); /*Allocate the drop down list type specific extended data*/ lv_ddlist_ext_t * ext = lv_obj_alloc_ext(new_ddlist, sizeof(lv_ddlist_ext_t)); dm_assert(ext); /*Initialize the allocated 'ext' */ ext->opt_label = NULL; ext->cb = NULL; ext->opened = 0; ext->auto_size = 0; ext->sel_opt = 0; /*The signal and design functions are not copied so set them here*/ if(ancestor_design_f == NULL) ancestor_design_f = lv_obj_get_design_f(new_ddlist); lv_obj_set_signal_f(new_ddlist, lv_ddlist_signal); lv_obj_set_design_f(new_ddlist, lv_ddlist_design); /*Init the new drop down list drop down list*/ if(copy == NULL) { lv_obj_set_drag(lv_page_get_scrl(new_ddlist), false); ext->opt_label = lv_label_create(new_ddlist, NULL); lv_rect_set_fit(new_ddlist, true, false); lv_page_set_rel_action(new_ddlist, lv_ddlist_rel_action); lv_obj_set_style(new_ddlist, lv_ddlists_get(LV_DDLISTS_DEF, NULL)); lv_ddlist_set_options(new_ddlist, def_options); } /*Copy an existing drop down list*/ else { lv_ddlist_ext_t * copy_ext = lv_obj_get_ext(copy); ext->opt_label = lv_label_create(new_ddlist, copy_ext->opt_label); lv_label_set_text(ext->opt_label, lv_label_get_text(copy_ext->opt_label)); ext->sel_opt = copy_ext->sel_opt; ext->auto_size = copy_ext->auto_size; ext->cb = copy_ext->cb; /*Refresh the style with new signal function*/ lv_obj_refr_style(new_ddlist); } return new_ddlist; } /** * Signal function of the drop down list * @param ddlist pointer to a drop down list object * @param sign a signal type from lv_signal_t enum * @param param pointer to a signal specific variable * @return true: the object is still valid (not deleted), false: the object become invalid */ bool lv_ddlist_signal(lv_obj_t * ddlist, lv_signal_t sign, void * param) { bool valid; /* Include the ancient signal function */ valid = lv_page_signal(ddlist, sign, param); /* The object can be deleted so check its validity and then * make the object specific signal handling */ if(valid != false) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); lv_ddlists_t * style = lv_obj_get_style(ddlist); switch(sign) { case LV_SIGNAL_CLEANUP: /*Nothing to cleanup. (No dynamically allocated memory in 'ext')*/ break; case LV_SIGNAL_STYLE_CHG: lv_obj_set_style(ext->opt_label, &style->list_labels); lv_ddlist_refr_size(ddlist, false); break; default: break; } } return valid; } /*===================== * Setter functions *====================*/ /** * Set the options in a drop down list * @param ddlist pointer to drop down list object * @param options an array of strings wit the text of the options. * The lest element has to be "" (empty string) * E.g. const char * opts[] = {"apple", "banana", "orange", ""}; */ void lv_ddlist_set_options(lv_obj_t * ddlist, const char ** options) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); lv_label_set_text(ext->opt_label, ""); uint16_t i = 0; while(options[i][0] != '\0') { lv_label_append_text(ext->opt_label, options[i]); if(options[i + 1][0] != '\0') lv_label_append_text(ext->opt_label, "\n"); i++; } lv_ddlist_refr_size(ddlist, false); } /** * Set the selected option * @param ddlist pointer to drop down list object * @param sel_opt id of the selected option (0 ... number of option - 1); */ void lv_ddlist_set_selected(lv_obj_t * ddlist, uint16_t sel_opt) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); ext->sel_opt = sel_opt; /*Move the list to show the current option*/ if(ext->opened == 0) { lv_ddlist_pos_act_option(ddlist); } } /** * Set a function to call when a new option is chosen * @param ddlist pointer to a drop down list * @param cb pointer to a call back function. Its prototype is: * parameter 1: pointer to the drop down list * parameter 2: id of the chosen item (0 ... number of options - 1) * return LV_ACTION_RES_INV if the drop down list is deleted in the function else LV_ACTION_RES_OK */ void lv_ddlist_set_action(lv_obj_t * ddlist, lv_action_res_t (*cb)(lv_obj_t *, uint16_t)) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); ext->cb = cb; } /** * Set the auto size attribute. If enabled the height will reduced to be visible on the parent. * In this case the drop down list can be scrolled. * @param ddlist pointer to a drop down list * @param auto_size true: enable auto size, false: disable */ void lv_ddlist_set_auto_size(lv_obj_t * ddlist, bool auto_size) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); ext->auto_size = auto_size == false ? 0 : 1; } /*===================== * Getter functions *====================*/ /** * Get the options of a drop down list * @param ddlist pointer to drop down list object * @return the options separated by '\n'-s (E.g. "Option1\nOption2\nOption3") */ const char * lv_ddlist_get_options(lv_obj_t * ddlist) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); return lv_label_get_text(ext->opt_label); } /** * Get the selected option * @param ddlist pointer to drop down list object * @return id of the selected option (0 ... number of option - 1); */ uint16_t lv_ddlist_get_selected(lv_obj_t * ddlist) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); return ext->sel_opt; } /** * Get the auto size attribute. * @param ddlist pointer to a drop down list * @return true: the auto_size is enabled, false: disabled */ bool lv_ddlist_get_auto_size(lv_obj_t * ddlist, bool auto_size) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); return ext->auto_size == 0 ? false : true; } /** * Return with a pointer to a built-in style and/or copy it to a variable * @param style a style name from lv_ddlists_builtin_t enum * @param copy copy the style to this variable. (NULL if unused) * @return pointer to an lv_ddlists_t style */ lv_ddlists_t * lv_ddlists_get(lv_ddlists_builtin_t style, lv_ddlists_t * copy) { static bool style_inited = false; /*Make the style initialization if it is not done yet*/ if(style_inited == false) { lv_ddlists_init(); style_inited = true; } lv_ddlists_t *style_p; switch(style) { case LV_DDLISTS_DEF: style_p = &lv_ddlists_def; break; default: style_p = &lv_ddlists_def; } if(copy != NULL) { if(style_p != NULL) memcpy(copy, style_p, sizeof(lv_ddlists_t)); else memcpy(copy, &lv_ddlists_def, sizeof(lv_ddlists_t)); } return style_p; } /********************** * STATIC FUNCTIONS **********************/ /** * Handle the drawing related tasks of the drop down lists * @param ddlist pointer to an object * @param mask the object will be drawn only in this area * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area * (return 'true' if yes) * LV_DESIGN_DRAW: draw the object (always return 'true') * LV_DESIGN_DRAW_POST: drawing after every children are drawn * @param return true/false, depends on 'mode' */ static bool lv_ddlist_design(lv_obj_t * ddlist, const area_t * mask, lv_design_mode_t mode) { /*Return false if the object is not covers the mask_p area*/ if(mode == LV_DESIGN_COVER_CHK) { return ancestor_design_f(ddlist, mask, mode); } /*Draw the object*/ else if(mode == LV_DESIGN_DRAW_MAIN) { ancestor_design_f(ddlist, mask, mode); /*If the list is opened draw a rectangle below the selected item*/ lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); if(ext->opened != 0) { lv_ddlists_t * style = lv_obj_get_style(ddlist); const font_t * font = font_get(style->list_labels.font); area_t rect_area; rect_area.y1 = ext->opt_label->cords.y1; rect_area.y1 += ext->sel_opt * (font_get_height(font) + style->list_labels.line_space); rect_area.y1 -= style->sel_rects.vpad; rect_area.y2 = rect_area.y1 + font_get_height(font) + 2 * style->sel_rects.vpad; rect_area.x1 = ext->opt_label->cords.x1 - style->pages.scrl_rects.hpad; rect_area.x2 = rect_area.x1 + lv_obj_get_width(lv_page_get_scrl(ddlist)); lv_draw_rect(&rect_area, mask, &style->sel_rects, OPA_COVER); } } /*Post draw when the children are drawn*/ else if(mode == LV_DESIGN_DRAW_POST) { ancestor_design_f(ddlist, mask, mode); } return true; } /** * Called when a drop down list is released to open it or set new option * @param ddlist pointer to a drop down list object * @param dispi pointer to the called display input * @return LV_ACTION_RES_INV if the ddlist it deleted in the user callback else LV_ACTION_RES_OK */ static lv_action_res_t lv_ddlist_rel_action(lv_obj_t * ddlist, lv_dispi_t * dispi) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); if(ext->opened == 0) { /*Open the list*/ ext->opened = 1; lv_obj_set_drag(lv_page_get_scrl(ddlist), true); } else { ext->opened = 0; lv_obj_set_drag(lv_page_get_scrl(ddlist), false); /*Search the clicked option*/ point_t p; lv_dispi_get_point(dispi, &p); p.x -= ext->opt_label->cords.x1; p.y -= ext->opt_label->cords.y1; uint16_t letter_i; letter_i = lv_label_get_letter_on(ext->opt_label, &p); uint16_t new_opt = 0; const char * txt = lv_label_get_text(ext->opt_label); uint16_t i; for(i = 0; i < letter_i; i++) { if(txt[i] == '\n') new_opt ++; } ext->sel_opt = new_opt; if(ext->cb != NULL) { ext->cb(ddlist, ext->sel_opt); } } #if LV_DDLIST_ANIM_TIME == 0 lv_ddlist_refr_size(ddlist, false); #else lv_ddlist_refr_size(ddlist, true); #endif return LV_ACTION_RES_OK; } /** * Refresh the size of drop down list according its start (open or closed) * @param ddlist poinr to a drop down list object * @param anim_en true: refresh the size with an animation, false: do not use animations */ static void lv_ddlist_refr_size(lv_obj_t * ddlist, bool anim_en) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); lv_ddlists_t * style = lv_obj_get_style(ddlist); cord_t new_height; if(ext->opened != 0) { /*Open the list*/ new_height = lv_obj_get_height(lv_page_get_scrl(ddlist)) + 2 * style->pages.bg_rects.vpad; lv_obj_t * parent = lv_obj_get_parent(ddlist); /*Reduce the height is enabler and required*/ if(ext->auto_size != 0 && new_height + ddlist->cords.y1 > parent->cords.y2) { new_height = parent->cords.y2 - ddlist->cords.y1; } } else { /*Close the list*/ const font_t * font = font_get(style->list_labels.font); new_height = font_get_height(font) + 2 * style->sel_rects.vpad; } if(anim_en == false) { lv_obj_set_height(ddlist, new_height); lv_ddlist_pos_act_option(ddlist); } else { anim_t a; a.var = ddlist; a.start = lv_obj_get_height(ddlist); a.end = new_height; a.fp = (anim_fp_t)lv_obj_set_height; a.path = anim_get_path(ANIM_PATH_LIN); a.end_cb = (anim_cb_t)lv_ddlist_pos_act_option; a.act_time = 0; a.time = LV_DDLIST_ANIM_TIME; a.playback = 0; a.playback_pause = 0; a.repeat = 0; a.repeat_pause = 0; anim_create(&a); } } /** * Set the position of list when it is closed to show the selected item * @param ddlist pointer to a drop down list */ static void lv_ddlist_pos_act_option(lv_obj_t * ddlist) { lv_ddlist_ext_t * ext = lv_obj_get_ext(ddlist); lv_ddlists_t * style = lv_obj_get_style(ddlist); const font_t * font = font_get(style->list_labels.font); lv_obj_set_y(lv_page_get_scrl(ddlist), -(ext->sel_opt * (font_get_height(font) + style->list_labels.line_space) + style->pages.scrl_rects.vpad) + style->sel_rects.vpad); } /** * Initialize the built-in drop down list styles */ static void lv_ddlists_init(void) { /*Default style*/ lv_pages_get(LV_PAGES_SIMPLE, &lv_ddlists_def.pages); lv_ddlists_def.pages.bg_rects.objs.color = COLOR_WHITE; lv_ddlists_def.pages.bg_rects.gcolor = COLOR_SILVER; lv_ddlists_def.pages.bg_rects.bcolor = COLOR_GRAY; lv_ddlists_def.pages.bg_rects.hpad = 0 * LV_DOWNSCALE; lv_ddlists_def.pages.bg_rects.vpad = 0 * LV_DOWNSCALE; lv_ddlists_def.pages.bg_rects.opad = 0; lv_ddlists_def.pages.scrl_rects.hpad = 5 * LV_DOWNSCALE; lv_ddlists_def.pages.scrl_rects.vpad = 10 * LV_DOWNSCALE; lv_ddlists_def.pages.scrl_rects.opad = 0 * LV_DOWNSCALE; lv_ddlists_def.pages.sb_mode = LV_PAGE_SB_MODE_OFF; lv_labels_get(LV_LABELS_DEF, &lv_ddlists_def.list_labels); lv_ddlists_def.list_labels.line_space = 15 * LV_DOWNSCALE; lv_rects_get(LV_RECTS_DEF, &lv_ddlists_def.sel_rects); lv_ddlists_def.sel_rects.bwidth = 0; lv_ddlists_def.sel_rects.round = 0; lv_ddlists_def.sel_rects.vpad = 7 * LV_DOWNSCALE; } #endif