1 /**
2  * @file lv_imgbtn.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 
10 #include "../lv_misc/lv_debug.h"
11 #include "../lv_themes/lv_theme.h"
12 #include "lv_imgbtn.h"
13 #include "lv_label.h"
14 
15 
16 #if LV_USE_IMGBTN != 0
17 
18 /*********************
19  *      DEFINES
20  *********************/
21 #define LV_OBJX_NAME "lv_imgbtn"
22 
23 /**********************
24  *      TYPEDEFS
25  **********************/
26 
27 /**********************
28  *  STATIC PROTOTYPES
29  **********************/
30 static lv_design_res_t lv_imgbtn_design(lv_obj_t * imgbtn, const lv_area_t * clip_area, lv_design_mode_t mode);
31 static lv_res_t lv_imgbtn_signal(lv_obj_t * imgbtn, lv_signal_t sign, void * param);
32 static void refr_img(lv_obj_t * imgbtn);
33 
34 /**********************
35  *  STATIC VARIABLES
36  **********************/
37 static lv_signal_cb_t ancestor_signal;
38 static lv_design_cb_t ancestor_design;
39 
40 /**********************
41  *      MACROS
42  **********************/
43 
44 /**********************
45  *   GLOBAL FUNCTIONS
46  **********************/
47 
48 /**
49  * Create a image button object
50  * @param par pointer to an object, it will be the parent of the new image button
51  * @param copy pointer to a image button object, if not NULL then the new object will be copied from
52  * it
53  * @return pointer to the created image button
54  */
lv_imgbtn_create(lv_obj_t * par,const lv_obj_t * copy)55 lv_obj_t * lv_imgbtn_create(lv_obj_t * par, const lv_obj_t * copy)
56 {
57     LV_LOG_TRACE("image button create started");
58 
59     /*Create the ancestor of image button*/
60     lv_obj_t * imgbtn = lv_btn_create(par, copy);
61     LV_ASSERT_MEM(imgbtn);
62     if(imgbtn == NULL) return NULL;
63 
64     /*Allocate the image button type specific extended data*/
65     lv_imgbtn_ext_t * ext = lv_obj_allocate_ext_attr(imgbtn, sizeof(lv_imgbtn_ext_t));
66     LV_ASSERT_MEM(ext);
67     if(ext == NULL) {
68         lv_obj_del(imgbtn);
69         return NULL;
70     }
71 
72     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(imgbtn);
73     if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(imgbtn);
74 
75     /*Initialize the allocated 'ext' */
76     _lv_memset_00((void *)ext->img_src_mid, sizeof(ext->img_src_mid));
77 #if LV_IMGBTN_TILED
78     _lv_memset_00(ext->img_src_left, sizeof(ext->img_src_left));
79     _lv_memset_00(ext->img_src_right, sizeof(ext->img_src_right));
80 #endif
81     ext->tiled = 0;
82 
83     ext->act_cf = LV_IMG_CF_UNKNOWN;
84 
85     /*The signal and design functions are not copied so set them here*/
86     lv_obj_set_signal_cb(imgbtn, lv_imgbtn_signal);
87     lv_obj_set_design_cb(imgbtn, lv_imgbtn_design);
88 
89     /*Init the new image button image button*/
90     if(copy == NULL) {
91         lv_theme_apply(imgbtn, LV_THEME_IMGBTN);
92     }
93     /*Copy an existing image button*/
94     else {
95         lv_imgbtn_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
96         _lv_memcpy((void *)ext->img_src_mid, copy_ext->img_src_mid, sizeof(ext->img_src_mid));
97 #if LV_IMGBTN_TILED
98         _lv_memcpy((void *)ext->img_src_left, copy_ext->img_src_left, sizeof(ext->img_src_left));
99         _lv_memcpy((void *)ext->img_src_right, copy_ext->img_src_right, sizeof(ext->img_src_right));
100 #endif
101         ext->tiled = copy_ext->tiled;
102         /*Refresh the style with new signal function*/
103         lv_obj_refresh_style(imgbtn, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL);
104     }
105 
106     LV_LOG_INFO("image button created");
107 
108     return imgbtn;
109 }
110 
111 /*=====================
112  * Setter functions
113  *====================*/
114 
115 /**
116  * Set images for a state of the image button
117  * @param imgbtn pointer to an image button object
118  * @param state for which state set the new image (from `lv_btn_state_t`) `
119  * @param src pointer to an image source (a C array or path to a file)
120  */
lv_imgbtn_set_src(lv_obj_t * imgbtn,lv_btn_state_t state,const void * src)121 void lv_imgbtn_set_src(lv_obj_t * imgbtn, lv_btn_state_t state, const void * src)
122 {
123     LV_ASSERT_OBJ(imgbtn, LV_OBJX_NAME);
124 
125     lv_imgbtn_ext_t * ext = lv_obj_get_ext_attr(imgbtn);
126 
127     ext->img_src_mid[state] = src;
128 #if LV_IMGBTN_TILED
129     ext->img_src_left[state] = NULL;
130     ext->img_src_right[state] = NULL;
131 #endif
132     ext->tiled = 0;
133     refr_img(imgbtn);
134 }
135 
136 #if LV_IMGBTN_TILED
137 /**
138  * Set images for a state of the image button
139  * @param imgbtn pointer to an image button object
140  * @param state for which state set the new image (from `lv_btn_state_t`) `
141  * @param src_left pointer to an image source for the left side of the button (a C array or path to
142  * a file)
143  * @param src_mid pointer to an image source for the middle of the button (ideally 1px wide) (a C
144  * array or path to a file)
145  * @param src_right pointer to an image source for the right side of the button (a C array or path
146  * to a file)
147  */
lv_imgbtn_set_src_tiled(lv_obj_t * imgbtn,lv_btn_state_t state,const void * src_left,const void * src_mid,const void * src_right)148 void lv_imgbtn_set_src_tiled(lv_obj_t * imgbtn, lv_btn_state_t state, const void * src_left, const void * src_mid,
149                              const void * src_right)
150 {
151     LV_ASSERT_OBJ(imgbtn, LV_OBJX_NAME);
152 
153     if(lv_img_src_get_type(src_left) == LV_IMG_SRC_SYMBOL ||
154        lv_img_src_get_type(src_mid) == LV_IMG_SRC_SYMBOL ||
155        lv_img_src_get_type(src_right) == LV_IMG_SRC_SYMBOL) {
156         LV_LOG_WARN("lv_imgbtn_set_src: symbols are not supported in tiled mode");
157         return;
158     }
159 
160     lv_imgbtn_ext_t * ext = lv_obj_get_ext_attr(imgbtn);
161 
162     ext->img_src_left[state] = src_left;
163     ext->img_src_mid[state] = src_mid;
164     ext->img_src_right[state] = src_right;
165 
166     ext->tiled = 1;
167 
168     refr_img(imgbtn);
169 }
170 
171 #endif
172 
173 /*=====================
174  * Getter functions
175  *====================*/
176 
177 /**
178  * Get the images in a  given state
179  * @param imgbtn pointer to an image button object
180  * @param state the state where to get the image (from `lv_btn_state_t`) `
181  * @return pointer to an image source (a C array or path to a file)
182  */
lv_imgbtn_get_src(lv_obj_t * imgbtn,lv_btn_state_t state)183 const void * lv_imgbtn_get_src(lv_obj_t * imgbtn, lv_btn_state_t state)
184 {
185     LV_ASSERT_OBJ(imgbtn, LV_OBJX_NAME);
186 
187     lv_imgbtn_ext_t * ext = lv_obj_get_ext_attr(imgbtn);
188 
189     return ext->img_src_mid[state];
190 }
191 #if LV_IMGBTN_TILED
192 
193 /**
194  * Get the left image in a given state
195  * @param imgbtn pointer to an image button object
196  * @param state the state where to get the image (from `lv_btn_state_t`) `
197  * @return pointer to the left image source (a C array or path to a file)
198  */
lv_imgbtn_get_src_left(lv_obj_t * imgbtn,lv_btn_state_t state)199 const void * lv_imgbtn_get_src_left(lv_obj_t * imgbtn, lv_btn_state_t state)
200 {
201     LV_ASSERT_OBJ(imgbtn, LV_OBJX_NAME);
202 
203     lv_imgbtn_ext_t * ext = lv_obj_get_ext_attr(imgbtn);
204 
205     return ext->img_src_left[state];
206 }
207 
208 /**
209  * Get the middle image in a given state
210  * @param imgbtn pointer to an image button object
211  * @param state the state where to get the image (from `lv_btn_state_t`) `
212  * @return pointer to the middle image source (a C array or path to a file)
213  */
lv_imgbtn_get_src_middle(lv_obj_t * imgbtn,lv_btn_state_t state)214 const void * lv_imgbtn_get_src_middle(lv_obj_t * imgbtn, lv_btn_state_t state)
215 {
216     LV_ASSERT_OBJ(imgbtn, LV_OBJX_NAME);
217 
218     lv_imgbtn_ext_t * ext = lv_obj_get_ext_attr(imgbtn);
219 
220     return ext->img_src_mid[state];
221 }
222 
223 /**
224  * Get the right image in a given state
225  * @param imgbtn pointer to an image button object
226  * @param state the state where to get the image (from `lv_btn_state_t`) `
227  * @return pointer to the left image source (a C array or path to a file)
228  */
lv_imgbtn_get_src_right(lv_obj_t * imgbtn,lv_btn_state_t state)229 const void * lv_imgbtn_get_src_right(lv_obj_t * imgbtn, lv_btn_state_t state)
230 {
231     LV_ASSERT_OBJ(imgbtn, LV_OBJX_NAME);
232 
233     lv_imgbtn_ext_t * ext = lv_obj_get_ext_attr(imgbtn);
234 
235     return ext->img_src_right[state];
236 }
237 
238 #endif
239 
240 /*=====================
241  * Other functions
242  *====================*/
243 
244 /*
245  * New object specific "other" functions come here
246  */
247 
248 /**********************
249  *   STATIC FUNCTIONS
250  **********************/
251 
252 /**
253  * Handle the drawing related tasks of the image buttons
254  * @param imgbtn pointer to an object
255  * @param clip_area the object will be drawn only in this area
256  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
257  *                                  (return 'true' if yes)
258  *             LV_DESIGN_DRAW: draw the object (always return 'true')
259  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
260  * @param return an element of `lv_design_res_t`
261  */
lv_imgbtn_design(lv_obj_t * imgbtn,const lv_area_t * clip_area,lv_design_mode_t mode)262 static lv_design_res_t lv_imgbtn_design(lv_obj_t * imgbtn, const lv_area_t * clip_area, lv_design_mode_t mode)
263 {
264     /*Return false if the object is not covers the mask_p area*/
265     if(mode == LV_DESIGN_COVER_CHK) {
266         lv_imgbtn_ext_t * ext = lv_obj_get_ext_attr(imgbtn);
267         lv_design_res_t cover = LV_DESIGN_RES_NOT_COVER;
268         if(ext->act_cf == LV_IMG_CF_TRUE_COLOR || ext->act_cf == LV_IMG_CF_RAW) {
269             cover = _lv_area_is_in(clip_area, &imgbtn->coords, 0) ? LV_DESIGN_RES_COVER : LV_DESIGN_RES_NOT_COVER;
270         }
271 
272         return cover;
273     }
274     /*Draw the object*/
275     else if(mode == LV_DESIGN_DRAW_MAIN) {
276         lv_area_t img_coords;
277 
278         lv_obj_get_coords(imgbtn, &img_coords);
279 
280         lv_draw_rect_dsc_t bg_dsc;
281         lv_draw_rect_dsc_init(&bg_dsc);
282         lv_obj_init_draw_rect_dsc(imgbtn, LV_IMGBTN_PART_MAIN, &bg_dsc);
283 
284         /*If the border is drawn later disable loading its properties*/
285         if(lv_obj_get_style_border_post(imgbtn, LV_OBJ_PART_MAIN)) {
286             bg_dsc.border_opa = LV_OPA_TRANSP;
287         }
288 
289         lv_area_t bg_coords;
290         lv_area_copy(&bg_coords, &img_coords);
291         bg_coords.x1 -= lv_obj_get_style_pad_left(imgbtn, LV_IMGBTN_PART_MAIN);
292         bg_coords.x2 += lv_obj_get_style_pad_right(imgbtn, LV_IMGBTN_PART_MAIN);
293         bg_coords.y1 -= lv_obj_get_style_pad_top(imgbtn, LV_IMGBTN_PART_MAIN);
294         bg_coords.y2 += lv_obj_get_style_pad_bottom(imgbtn, LV_IMGBTN_PART_MAIN);
295 
296         lv_draw_rect(&bg_coords, clip_area, &bg_dsc);
297 
298         if(lv_obj_get_style_clip_corner(imgbtn, LV_OBJ_PART_MAIN)) {
299             lv_draw_mask_radius_param_t * mp = _lv_mem_buf_get(sizeof(lv_draw_mask_radius_param_t));
300 
301             lv_coord_t r = lv_obj_get_style_radius(imgbtn, LV_OBJ_PART_MAIN);
302 
303             lv_draw_mask_radius_init(mp, &bg_coords, r, false);
304             /*Add the mask and use `img+8` as custom id. Don't use `obj` directly because it might be used by the user*/
305             lv_draw_mask_add(mp, imgbtn + 8);
306         }
307 
308         /*Just draw an image*/
309         lv_imgbtn_ext_t * ext    = lv_obj_get_ext_attr(imgbtn);
310         lv_btn_state_t state     = lv_imgbtn_get_state(imgbtn);
311 
312         /*Simply draw the middle src if no tiled*/
313         if(!ext->tiled) {
314             const void * src = ext->img_src_mid[state];
315             if(lv_img_src_get_type(src) == LV_IMG_SRC_SYMBOL) {
316                 lv_draw_label_dsc_t label_dsc;
317                 lv_draw_label_dsc_init(&label_dsc);
318                 lv_obj_init_draw_label_dsc(imgbtn, LV_IMGBTN_PART_MAIN, &label_dsc);
319                 lv_draw_label(&imgbtn->coords, clip_area, &label_dsc, src, NULL);
320             }
321             else {
322                 lv_draw_img_dsc_t img_dsc;
323                 lv_draw_img_dsc_init(&img_dsc);
324                 lv_obj_init_draw_img_dsc(imgbtn, LV_IMGBTN_PART_MAIN, &img_dsc);
325                 lv_draw_img(&imgbtn->coords, clip_area, src, &img_dsc);
326             }
327         }
328         else {
329 #if LV_IMGBTN_TILED
330             const void * src = ext->img_src_left[state];
331             if(lv_img_src_get_type(src) == LV_IMG_SRC_SYMBOL) {
332                 LV_LOG_WARN("lv_imgbtn_design: SYMBOLS are not supported in tiled mode")
333                 return LV_DESIGN_RES_OK;
334             }
335 
336             lv_coord_t w = lv_obj_get_style_transform_width(imgbtn, LV_OBJ_PART_MAIN);
337             lv_coord_t h = lv_obj_get_style_transform_height(imgbtn, LV_OBJ_PART_MAIN);
338             lv_area_t coords;
339             lv_area_copy(&coords, &imgbtn->coords);
340             coords.x1 -= w;
341             coords.x2 += w;
342             coords.y1 -= h;
343             coords.y2 += h;
344 
345             lv_draw_img_dsc_t img_dsc;
346             lv_draw_img_dsc_init(&img_dsc);
347             lv_obj_init_draw_img_dsc(imgbtn, LV_IMGBTN_PART_MAIN, &img_dsc);
348 
349             lv_img_header_t header;
350             lv_area_t coords_part;
351             lv_coord_t left_w = 0;
352             lv_coord_t right_w = 0;
353 
354             if(src) {
355                 lv_img_decoder_get_info(src, &header);
356                 left_w = header.w;
357                 coords_part.x1 = coords.x1;
358                 coords_part.y1 = coords.y1;
359                 coords_part.x2 = coords.x1 + header.w - 1;
360                 coords_part.y2 = coords.y1 + header.h - 1;
361                 lv_draw_img(&coords_part, clip_area, src, &img_dsc);
362             }
363 
364             src = ext->img_src_right[state];
365             if(src) {
366                 lv_img_decoder_get_info(src, &header);
367                 right_w = header.w;
368                 coords_part.x1 = coords.x2 - header.w + 1;
369                 coords_part.y1 = coords.y1;
370                 coords_part.x2 = coords.x2;
371                 coords_part.y2 = coords.y1 + header.h - 1;
372                 lv_draw_img(&coords_part, clip_area, src, &img_dsc);
373             }
374 
375             src = ext->img_src_mid[state];
376             if(src) {
377                 lv_area_t clip_center_area;
378                 clip_center_area.x1 = coords.x1 + left_w;
379                 clip_center_area.x2 = coords.x2 - right_w;
380                 clip_center_area.y1 = coords.y1;
381                 clip_center_area.y2 = coords.y2;
382 
383                 bool comm_res;
384                 comm_res = _lv_area_intersect(&clip_center_area, &clip_center_area, clip_area);
385                 if(comm_res) {
386                     lv_coord_t obj_w = lv_obj_get_width(imgbtn);
387                     lv_coord_t i;
388                     lv_img_decoder_get_info(src, &header);
389 
390                     coords_part.x1 = coords.x1 + left_w;
391                     coords_part.y1 = coords.y1;
392                     coords_part.x2 = coords_part.x1 + header.w - 1;
393                     coords_part.y2 = coords_part.y1 + header.h - 1;
394 
395                     for(i = 0; i < obj_w - right_w - left_w; i += header.w) {
396 
397                         lv_draw_img(&coords_part, &clip_center_area, src, &img_dsc);
398                         coords_part.x1 = coords_part.x2 + 1;
399                         coords_part.x2 += header.w;
400                     }
401                 }
402             }
403 #endif
404         }
405     }
406     /*Post draw when the children are drawn*/
407     else if(mode == LV_DESIGN_DRAW_POST) {
408         if(lv_obj_get_style_clip_corner(imgbtn, LV_OBJ_PART_MAIN)) {
409             lv_draw_mask_radius_param_t * param = lv_draw_mask_remove_custom(imgbtn + 8);
410             _lv_mem_buf_release(param);
411         }
412 
413         lv_draw_rect_dsc_t draw_dsc;
414         lv_draw_rect_dsc_init(&draw_dsc);
415 
416         /*If the border is drawn later disable loading other properties*/
417         if(lv_obj_get_style_border_post(imgbtn, LV_OBJ_PART_MAIN)) {
418             draw_dsc.bg_opa = LV_OPA_TRANSP;
419             draw_dsc.pattern_opa = LV_OPA_TRANSP;
420             draw_dsc.shadow_opa = LV_OPA_TRANSP;
421             lv_obj_init_draw_rect_dsc(imgbtn, LV_OBJ_PART_MAIN, &draw_dsc);
422 
423 
424             lv_area_t bg_coords;
425             lv_area_copy(&bg_coords, &imgbtn->coords);
426             bg_coords.x1 -= lv_obj_get_style_pad_left(imgbtn, LV_IMGBTN_PART_MAIN);
427             bg_coords.x2 += lv_obj_get_style_pad_right(imgbtn, LV_IMGBTN_PART_MAIN);
428             bg_coords.y1 -= lv_obj_get_style_pad_top(imgbtn, LV_IMGBTN_PART_MAIN);
429             bg_coords.y2 += lv_obj_get_style_pad_bottom(imgbtn, LV_IMGBTN_PART_MAIN);
430 
431             lv_draw_rect(&bg_coords, clip_area, &draw_dsc);
432         }
433     }
434 
435     return LV_DESIGN_RES_OK;
436 }
437 
438 /**
439  * Signal function of the image button
440  * @param imgbtn pointer to a image button object
441  * @param sign a signal type from lv_signal_t enum
442  * @param param pointer to a signal specific variable
443  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
444  */
lv_imgbtn_signal(lv_obj_t * imgbtn,lv_signal_t sign,void * param)445 static lv_res_t lv_imgbtn_signal(lv_obj_t * imgbtn, lv_signal_t sign, void * param)
446 {
447     lv_res_t res;
448 
449     /* Include the ancient signal function */
450     res = ancestor_signal(imgbtn, sign, param);
451     if(res != LV_RES_OK) return res;
452     if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
453 
454     if(sign == LV_SIGNAL_STYLE_CHG) {
455         /* If the style changed then the button was clicked, released etc. so probably the state was
456          * changed as well Set the new image for the new state.*/
457         refr_img(imgbtn);
458     }
459     else if(sign == LV_SIGNAL_REFR_EXT_DRAW_PAD) {
460         /*Handle the padding of the background*/
461         lv_style_int_t left = lv_obj_get_style_pad_left(imgbtn, LV_IMGBTN_PART_MAIN);
462         lv_style_int_t right = lv_obj_get_style_pad_right(imgbtn, LV_IMGBTN_PART_MAIN);
463         lv_style_int_t top = lv_obj_get_style_pad_top(imgbtn, LV_IMGBTN_PART_MAIN);
464         lv_style_int_t bottom = lv_obj_get_style_pad_bottom(imgbtn, LV_IMGBTN_PART_MAIN);
465 
466         imgbtn->ext_draw_pad = LV_MATH_MAX(imgbtn->ext_draw_pad, left);
467         imgbtn->ext_draw_pad = LV_MATH_MAX(imgbtn->ext_draw_pad, right);
468         imgbtn->ext_draw_pad = LV_MATH_MAX(imgbtn->ext_draw_pad, top);
469         imgbtn->ext_draw_pad = LV_MATH_MAX(imgbtn->ext_draw_pad, bottom);
470     }
471     else if(sign == LV_SIGNAL_PRESSED || sign == LV_SIGNAL_RELEASED || sign == LV_SIGNAL_PRESS_LOST) {
472         refr_img(imgbtn);
473     }
474     else if(sign == LV_SIGNAL_CLEANUP) {
475         /*Nothing to cleanup. (No dynamically allocated memory in 'ext')*/
476     }
477 
478     return res;
479 }
480 
refr_img(lv_obj_t * imgbtn)481 static void refr_img(lv_obj_t * imgbtn)
482 {
483     lv_imgbtn_ext_t * ext = lv_obj_get_ext_attr(imgbtn);
484     lv_btn_state_t state  = lv_imgbtn_get_state(imgbtn);
485     lv_img_header_t header;
486 
487     const void * src = ext->img_src_mid[state];
488     if(src == NULL) return;
489 
490     lv_res_t info_res = LV_RES_OK;
491     if(lv_img_src_get_type(src) == LV_IMG_SRC_SYMBOL) {
492         const lv_font_t * font = lv_obj_get_style_text_font(imgbtn, LV_IMGBTN_PART_MAIN);
493         header.h = lv_font_get_line_height(font);
494         header.w = _lv_txt_get_width(src, (uint16_t)strlen(src), font, 0, LV_TXT_FLAG_NONE);
495         header.always_zero = 0;
496         header.cf = LV_IMG_CF_ALPHA_1BIT;
497     }
498     else {
499         info_res = lv_img_decoder_get_info(src, &header);
500     }
501 
502     if(info_res == LV_RES_OK) {
503         ext->act_cf = header.cf;
504         if(ext->tiled) lv_obj_set_height(imgbtn, header.h); /*Keep the sure defined width*/
505         else  lv_obj_set_size(imgbtn, header.w, header.h);
506     }
507     else {
508         ext->act_cf = LV_IMG_CF_UNKNOWN;
509     }
510 
511     lv_obj_invalidate(imgbtn);
512 }
513 
514 #endif
515