1 /**
2  * @file lv_mbox.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_mbox.h"
10 #if LV_USE_MBOX != 0
11 
12 #include "../lv_core/lv_group.h"
13 #include "../lv_themes/lv_theme.h"
14 #include "../lv_misc/lv_anim.h"
15 #include "../lv_misc/lv_math.h"
16 
17 /*********************
18  *      DEFINES
19  *********************/
20 
21 #if LV_USE_ANIMATION
22 #ifndef LV_MBOX_CLOSE_ANIM_TIME
23 #define LV_MBOX_CLOSE_ANIM_TIME 200 /*List close animation time)  */
24 #endif
25 #else
26 #undef LV_MBOX_CLOSE_ANIM_TIME
27 #define LV_MBOX_CLOSE_ANIM_TIME 0 /*No animations*/
28 #endif
29 
30 /**********************
31  *      TYPEDEFS
32  **********************/
33 
34 /**********************
35  *  STATIC PROTOTYPES
36  **********************/
37 static lv_res_t lv_mbox_signal(lv_obj_t * mbox, lv_signal_t sign, void * param);
38 static void mbox_realign(lv_obj_t * mbox);
39 #if LV_USE_ANIMATION
40 static void lv_mbox_close_ready_cb(lv_anim_t * a);
41 #endif
42 static void lv_mbox_default_event_cb(lv_obj_t * mbox, lv_event_t event);
43 static void lv_mbox_btnm_event_cb(lv_obj_t * btnm, lv_event_t event);
44 
45 /**********************
46  *  STATIC VARIABLES
47  **********************/
48 static lv_signal_cb_t ancestor_signal;
49 
50 /**********************
51  *      MACROS
52  **********************/
53 
54 /**********************
55  *   GLOBAL FUNCTIONS
56  **********************/
57 
58 /**
59  * Create a message box objects
60  * @param par pointer to an object, it will be the parent of the new message box
61  * @param copy pointer to a message box object, if not NULL then the new object will be copied from
62  * it
63  * @return pointer to the created message box
64  */
lv_mbox_create(lv_obj_t * par,const lv_obj_t * copy)65 lv_obj_t * lv_mbox_create(lv_obj_t * par, const lv_obj_t * copy)
66 {
67     LV_LOG_TRACE("mesasge box create started");
68 
69     /*Create the ancestor message box*/
70     lv_obj_t * new_mbox = lv_cont_create(par, copy);
71     lv_mem_assert(new_mbox);
72     if(new_mbox == NULL) return NULL;
73 
74     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(new_mbox);
75 
76     /*Allocate the message box type specific extended data*/
77     lv_mbox_ext_t * ext = lv_obj_allocate_ext_attr(new_mbox, sizeof(lv_mbox_ext_t));
78     lv_mem_assert(ext);
79     if(ext == NULL) return NULL;
80 
81     ext->text = NULL;
82     ext->btnm = NULL;
83 #if LV_USE_ANIMATION
84     ext->anim_time = LV_MBOX_CLOSE_ANIM_TIME;
85 #endif
86 
87     /*The signal and design functions are not copied so set them here*/
88     lv_obj_set_signal_cb(new_mbox, lv_mbox_signal);
89 
90     /*Init the new message box message box*/
91     if(copy == NULL) {
92         ext->text = lv_label_create(new_mbox, NULL);
93         lv_label_set_align(ext->text, LV_LABEL_ALIGN_CENTER);
94         lv_label_set_long_mode(ext->text, LV_LABEL_LONG_BREAK);
95         lv_label_set_text(ext->text, "Message");
96 
97         lv_cont_set_layout(new_mbox, LV_LAYOUT_COL_M);
98         lv_cont_set_fit2(new_mbox, LV_FIT_NONE, LV_FIT_TIGHT);
99         lv_obj_set_width(new_mbox, LV_DPI * 2);
100         lv_obj_align(new_mbox, NULL, LV_ALIGN_CENTER, 0, 0);
101         lv_obj_set_event_cb(new_mbox, lv_mbox_default_event_cb);
102 
103         /*Set the default styles*/
104         lv_theme_t * th = lv_theme_get_current();
105         if(th) {
106             lv_mbox_set_style(new_mbox, LV_MBOX_STYLE_BG, th->style.mbox.bg);
107         } else {
108             lv_mbox_set_style(new_mbox, LV_MBOX_STYLE_BG, &lv_style_pretty);
109         }
110 
111     }
112     /*Copy an existing message box*/
113     else {
114         lv_mbox_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
115 
116         ext->text = lv_label_create(new_mbox, copy_ext->text);
117 
118         /*Copy the buttons and the label on them*/
119         if(copy_ext->btnm) ext->btnm = lv_btnm_create(new_mbox, copy_ext->btnm);
120 
121         /*Refresh the style with new signal function*/
122         lv_obj_refresh_style(new_mbox);
123     }
124 
125     LV_LOG_INFO("mesasge box created");
126 
127     return new_mbox;
128 }
129 
130 /*======================
131  * Add/remove functions
132  *=====================*/
133 
134 /**
135  * Add button to the message box
136  * @param mbox pointer to message box object
137  * @param btn_map button descriptor (button matrix map).
138  *                E.g.  a const char *txt[] = {"ok", "close", ""} (Can not be local variable)
139  */
lv_mbox_add_btns(lv_obj_t * mbox,const char ** btn_map)140 void lv_mbox_add_btns(lv_obj_t * mbox, const char ** btn_map)
141 {
142     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
143 
144     /*Create a button matrix if not exists yet*/
145     if(ext->btnm == NULL) {
146         ext->btnm = lv_btnm_create(mbox, NULL);
147 
148         /*Set the default styles*/
149         lv_theme_t * th = lv_theme_get_current();
150         if(th) {
151             lv_mbox_set_style(mbox, LV_MBOX_STYLE_BTN_BG, th->style.mbox.btn.bg);
152             lv_mbox_set_style(mbox, LV_MBOX_STYLE_BTN_REL, th->style.mbox.btn.rel);
153             lv_mbox_set_style(mbox, LV_MBOX_STYLE_BTN_PR, th->style.mbox.btn.pr);
154         } else {
155             lv_btnm_set_style(ext->btnm, LV_BTNM_STYLE_BG, &lv_style_transp_fit);
156         }
157     }
158 
159     lv_btnm_set_map(ext->btnm, btn_map);
160     lv_btnm_set_btn_ctrl_all(ext->btnm, LV_BTNM_CTRL_CLICK_TRIG | LV_BTNM_CTRL_NO_REPEAT);
161     lv_obj_set_event_cb(ext->btnm, lv_mbox_btnm_event_cb);
162 
163     mbox_realign(mbox);
164 }
165 
166 /*=====================
167  * Setter functions
168  *====================*/
169 
170 /**
171  * Set the text of the message box
172  * @param mbox pointer to a message box
173  * @param txt a '\0' terminated character string which will be the message box text
174  */
lv_mbox_set_text(lv_obj_t * mbox,const char * txt)175 void lv_mbox_set_text(lv_obj_t * mbox, const char * txt)
176 {
177     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
178     lv_label_set_text(ext->text, txt);
179 
180     mbox_realign(mbox);
181 }
182 
183 /**
184  * Set animation duration
185  * @param mbox pointer to a message box object
186  * @param anim_time animation length in  milliseconds (0: no animation)
187  */
lv_mbox_set_anim_time(lv_obj_t * mbox,uint16_t anim_time)188 void lv_mbox_set_anim_time(lv_obj_t * mbox, uint16_t anim_time)
189 {
190 #if LV_USE_ANIMATION
191     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
192     anim_time           = 0;
193     ext->anim_time      = anim_time;
194 #else
195     (void)mbox;
196     (void)anim_time;
197 #endif
198 }
199 
200 /**
201  * Automatically delete the message box after a given time
202  * @param mbox pointer to a message box object
203  * @param delay a time (in milliseconds) to wait before delete the message box
204  */
lv_mbox_start_auto_close(lv_obj_t * mbox,uint16_t delay)205 void lv_mbox_start_auto_close(lv_obj_t * mbox, uint16_t delay)
206 {
207 #if LV_USE_ANIMATION
208     if(lv_mbox_get_anim_time(mbox) != 0) {
209         /*Add shrinking animations*/
210         lv_anim_t a;
211         a.var            = mbox;
212         a.start          = lv_obj_get_height(mbox);
213         a.end            = 0;
214         a.exec_cb        = (lv_anim_exec_xcb_t)lv_obj_set_height;
215         a.path_cb        = lv_anim_path_linear;
216         a.ready_cb       = NULL;
217         a.act_time       = -delay;
218         a.time           = lv_mbox_get_anim_time(mbox);
219         a.playback       = 0;
220         a.playback_pause = 0;
221         a.repeat         = 0;
222         a.repeat_pause   = 0;
223         lv_anim_create(&a);
224 
225         a.start    = lv_obj_get_width(mbox);
226         a.exec_cb  = (lv_anim_exec_xcb_t)lv_obj_set_width;
227         a.ready_cb = lv_mbox_close_ready_cb;
228         lv_anim_create(&a);
229 
230         /*Disable fit to let shrinking work*/
231         lv_cont_set_fit(mbox, LV_FIT_NONE);
232     } else {
233         /*Create an animation to delete the mbox `delay` ms later*/
234         lv_anim_t a;
235         a.var            = mbox;
236         a.start          = 0;
237         a.end            = 1;
238         a.exec_cb        = (lv_anim_exec_xcb_t)NULL;
239         a.path_cb        = lv_anim_path_linear;
240         a.ready_cb       = lv_mbox_close_ready_cb;
241         a.act_time       = -delay;
242         a.time           = 0;
243         a.playback       = 0;
244         a.playback_pause = 0;
245         a.repeat         = 0;
246         a.repeat_pause   = 0;
247         lv_anim_create(&a);
248     }
249 #else
250     (void)delay; /*Unused*/
251     lv_obj_del(mbox);
252 #endif
253 }
254 
255 /**
256  * Stop the auto. closing of message box
257  * @param mbox pointer to a message box object
258  */
lv_mbox_stop_auto_close(lv_obj_t * mbox)259 void lv_mbox_stop_auto_close(lv_obj_t * mbox)
260 {
261 #if LV_USE_ANIMATION
262     lv_anim_del(mbox, NULL);
263 #else
264     (void)mbox; /*Unused*/
265 #endif
266 }
267 
268 /**
269  * Set a style of a message box
270  * @param mbox pointer to a message box object
271  * @param type which style should be set
272  * @param style pointer to a style
273  */
lv_mbox_set_style(lv_obj_t * mbox,lv_mbox_style_t type,const lv_style_t * style)274 void lv_mbox_set_style(lv_obj_t * mbox, lv_mbox_style_t type, const lv_style_t * style)
275 {
276     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
277 
278     switch(type) {
279         case LV_MBOX_STYLE_BG: lv_obj_set_style(mbox, style); break;
280         case LV_MBOX_STYLE_BTN_BG: lv_btnm_set_style(ext->btnm, LV_BTNM_STYLE_BG, style); break;
281         case LV_MBOX_STYLE_BTN_REL: lv_btnm_set_style(ext->btnm, LV_BTNM_STYLE_BTN_REL, style); break;
282         case LV_MBOX_STYLE_BTN_PR: lv_btnm_set_style(ext->btnm, LV_BTNM_STYLE_BTN_PR, style); break;
283         case LV_MBOX_STYLE_BTN_TGL_REL: lv_btnm_set_style(ext->btnm, LV_BTNM_STYLE_BTN_TGL_REL, style); break;
284         case LV_MBOX_STYLE_BTN_TGL_PR: lv_btnm_set_style(ext->btnm, LV_BTNM_STYLE_BTN_TGL_PR, style); break;
285         case LV_MBOX_STYLE_BTN_INA: lv_btnm_set_style(ext->btnm, LV_BTNM_STYLE_BTN_INA, style); break;
286     }
287 
288     mbox_realign(mbox);
289 }
290 
291 /**
292  * Set whether recoloring is enabled
293  * @param btnm pointer to button matrix object
294  * @param en whether recoloring is enabled
295  */
lv_mbox_set_recolor(lv_obj_t * mbox,bool en)296 void lv_mbox_set_recolor(lv_obj_t * mbox, bool en)
297 {
298     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
299 
300     if(ext->btnm) lv_btnm_set_recolor(ext->btnm, en);
301 }
302 
303 /*=====================
304  * Getter functions
305  *====================*/
306 
307 /**
308  * Get the text of the message box
309  * @param mbox pointer to a message box object
310  * @return pointer to the text of the message box
311  */
lv_mbox_get_text(const lv_obj_t * mbox)312 const char * lv_mbox_get_text(const lv_obj_t * mbox)
313 {
314     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
315 
316     return lv_label_get_text(ext->text);
317 }
318 
319 /**
320  * Get the index of the lastly "activated" button by the user (pressed, released etc)
321  * Useful in the the `event_cb`.
322  * @param btnm pointer to button matrix object
323  * @return  index of the last released button (LV_BTNM_BTN_NONE: if unset)
324  */
lv_mbox_get_active_btn(lv_obj_t * mbox)325 uint16_t lv_mbox_get_active_btn(lv_obj_t * mbox)
326 {
327     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
328     if(ext->btnm)
329         return lv_btnm_get_active_btn(ext->btnm);
330     else
331         return LV_BTNM_BTN_NONE;
332 }
333 
334 /**
335  * Get the text of the lastly "activated" button by the user (pressed, released etc)
336  * Useful in the the `event_cb`.
337  * @param btnm pointer to button matrix object
338  * @return text of the last released button (NULL: if unset)
339  */
lv_mbox_get_active_btn_text(lv_obj_t * mbox)340 const char * lv_mbox_get_active_btn_text(lv_obj_t * mbox)
341 {
342     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
343     if(ext->btnm)
344         return lv_btnm_get_active_btn_text(ext->btnm);
345     else
346         return NULL;
347 }
348 
349 /**
350  * Get the animation duration (close animation time)
351  * @param mbox pointer to a message box object
352  * @return animation length in  milliseconds (0: no animation)
353  */
lv_mbox_get_anim_time(const lv_obj_t * mbox)354 uint16_t lv_mbox_get_anim_time(const lv_obj_t * mbox)
355 {
356 #if LV_USE_ANIMATION
357     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
358     return ext->anim_time;
359 #else
360     (void)mbox;
361     return 0;
362 #endif
363 }
364 
365 /**
366  * Get a style of a message box
367  * @param mbox pointer to a message box object
368  * @param type which style should be get
369  * @return style pointer to a style
370  */
lv_mbox_get_style(const lv_obj_t * mbox,lv_mbox_style_t type)371 const lv_style_t * lv_mbox_get_style(const lv_obj_t * mbox, lv_mbox_style_t type)
372 {
373     const lv_style_t * style = NULL;
374     lv_mbox_ext_t * ext      = lv_obj_get_ext_attr(mbox);
375 
376     switch(type) {
377         case LV_MBOX_STYLE_BG: style = lv_obj_get_style(mbox); break;
378         case LV_MBOX_STYLE_BTN_BG: style = lv_btnm_get_style(ext->btnm, LV_BTNM_STYLE_BG); break;
379         case LV_MBOX_STYLE_BTN_REL: style = lv_btnm_get_style(ext->btnm, LV_BTNM_STYLE_BTN_REL); break;
380         case LV_MBOX_STYLE_BTN_PR: style = lv_btnm_get_style(ext->btnm, LV_BTNM_STYLE_BTN_PR); break;
381         case LV_MBOX_STYLE_BTN_TGL_REL: style = lv_btnm_get_style(ext->btnm, LV_BTNM_STYLE_BTN_TGL_REL); break;
382         case LV_MBOX_STYLE_BTN_TGL_PR: style = lv_btnm_get_style(ext->btnm, LV_BTNM_STYLE_BTN_TGL_PR); break;
383         case LV_MBOX_STYLE_BTN_INA: style = lv_btnm_get_style(ext->btnm, LV_BTNM_STYLE_BTN_INA); break;
384         default: style = NULL; break;
385     }
386 
387     return style;
388 }
389 
390 /**
391  * Get whether recoloring is enabled
392  * @param mbox pointer to a message box object
393  * @return whether recoloring is enabled
394  */
lv_mbox_get_recolor(const lv_obj_t * mbox)395 bool lv_mbox_get_recolor(const lv_obj_t * mbox)
396 {
397     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
398 
399     if(!ext->btnm) return false;
400 
401     return lv_btnm_get_recolor(ext->btnm);
402 }
403 
404 /**
405  * Get message box button matrix
406  * @param mbox pointer to a message box object
407  * @return pointer to button matrix object
408  * @remarks return value will be NULL unless `lv_mbox_add_btns` has been already called
409  */
lv_mbox_get_btnm(lv_obj_t * mbox)410 lv_obj_t * lv_mbox_get_btnm(lv_obj_t * mbox)
411 {
412     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
413     return ext->btnm;
414 }
415 
416 /**********************
417  *   STATIC FUNCTIONS
418  **********************/
419 
420 /**
421  * Signal function of the message box
422  * @param mbox pointer to a message box object
423  * @param sign a signal type from lv_signal_t enum
424  * @param param pointer to a signal specific variable
425  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
426  */
lv_mbox_signal(lv_obj_t * mbox,lv_signal_t sign,void * param)427 static lv_res_t lv_mbox_signal(lv_obj_t * mbox, lv_signal_t sign, void * param)
428 {
429     lv_res_t res;
430 
431     /*Translate LV_KEY_UP/DOWN to LV_KEY_LEFT/RIGHT */
432     char c_trans = 0;
433     if(sign == LV_SIGNAL_CONTROL) {
434         c_trans = *((char *)param);
435         if(c_trans == LV_KEY_DOWN) c_trans = LV_KEY_LEFT;
436         if(c_trans == LV_KEY_UP) c_trans = LV_KEY_RIGHT;
437 
438         param = &c_trans;
439     }
440 
441     /* Include the ancient signal function */
442     res = ancestor_signal(mbox, sign, param);
443     if(res != LV_RES_OK) return res;
444 
445     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
446     if(sign == LV_SIGNAL_CORD_CHG) {
447         if(lv_obj_get_width(mbox) != lv_area_get_width(param)) {
448             mbox_realign(mbox);
449         }
450     } else if(sign == LV_SIGNAL_STYLE_CHG) {
451         mbox_realign(mbox);
452     } else if(sign == LV_SIGNAL_RELEASED) {
453         if(ext->btnm) {
454             uint32_t btn_id = lv_btnm_get_active_btn(ext->btnm);
455             if(btn_id != LV_BTNM_BTN_NONE) lv_event_send(mbox, LV_EVENT_VALUE_CHANGED, &btn_id);
456         }
457     } else if(sign == LV_SIGNAL_FOCUS || sign == LV_SIGNAL_DEFOCUS || sign == LV_SIGNAL_CONTROL ||
458               sign == LV_SIGNAL_GET_EDITABLE) {
459         if(ext->btnm) {
460             ext->btnm->signal_cb(ext->btnm, sign, param);
461         }
462 
463         /* The button matrix with ENCODER input supposes it's in a group but in this case it isn't
464          * (Only the message box's container) So so some actions here instead*/
465         if(sign == LV_SIGNAL_FOCUS) {
466 #if LV_USE_GROUP
467             lv_indev_t * indev         = lv_indev_get_act();
468             lv_indev_type_t indev_type = lv_indev_get_type(indev);
469             if(indev_type == LV_INDEV_TYPE_ENCODER) {
470                 /*In navigation mode don't select any button but in edit mode select the fist*/
471                 lv_btnm_ext_t * btnm_ext = lv_obj_get_ext_attr(ext->btnm);
472                 if(lv_group_get_editing(lv_obj_get_group(mbox)))
473                     btnm_ext->btn_id_pr = 0;
474                 else
475                     btnm_ext->btn_id_pr = LV_BTNM_BTN_NONE;
476             }
477 #endif
478         }
479     } else if(sign == LV_SIGNAL_GET_TYPE) {
480         lv_obj_type_t * buf = param;
481         uint8_t i;
482         for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) { /*Find the last set data*/
483             if(buf->type[i] == NULL) break;
484         }
485         buf->type[i] = "lv_mbox";
486     }
487 
488     return res;
489 }
490 
491 /**
492  * Resize the button holder to fit
493  * @param mbox pointer to message box object
494  */
mbox_realign(lv_obj_t * mbox)495 static void mbox_realign(lv_obj_t * mbox)
496 {
497     lv_mbox_ext_t * ext = lv_obj_get_ext_attr(mbox);
498 
499     const lv_style_t * style = lv_mbox_get_style(mbox, LV_MBOX_STYLE_BG);
500     lv_coord_t w             = lv_obj_get_width(mbox) - style->body.padding.left - style->body.padding.right;
501 
502     if(ext->text) {
503         lv_obj_set_width(ext->text, w);
504     }
505 
506     if(ext->btnm) {
507         const lv_style_t * btn_bg_style  = lv_mbox_get_style(mbox, LV_MBOX_STYLE_BTN_BG);
508         const lv_style_t * btn_rel_style = lv_mbox_get_style(mbox, LV_MBOX_STYLE_BTN_REL);
509         lv_coord_t font_h                = lv_font_get_line_height(btn_rel_style->text.font);
510         lv_obj_set_size(ext->btnm, w,
511                         font_h + btn_rel_style->body.padding.top + btn_rel_style->body.padding.bottom +
512                             btn_bg_style->body.padding.top + btn_bg_style->body.padding.bottom);
513     }
514 }
515 
516 #if LV_USE_ANIMATION
lv_mbox_close_ready_cb(lv_anim_t * a)517 static void lv_mbox_close_ready_cb(lv_anim_t * a)
518 {
519     lv_obj_del(a->var);
520 }
521 #endif
522 
lv_mbox_default_event_cb(lv_obj_t * mbox,lv_event_t event)523 static void lv_mbox_default_event_cb(lv_obj_t * mbox, lv_event_t event)
524 {
525     if(event != LV_EVENT_VALUE_CHANGED) return;
526 
527     uint32_t btn_id = lv_mbox_get_active_btn(mbox);
528     if(btn_id == LV_BTNM_BTN_NONE) return;
529 
530     lv_mbox_start_auto_close(mbox, 0);
531 }
532 
lv_mbox_btnm_event_cb(lv_obj_t * btnm,lv_event_t event)533 static void lv_mbox_btnm_event_cb(lv_obj_t * btnm, lv_event_t event)
534 {
535     lv_obj_t * mbox = lv_obj_get_parent(btnm);
536 
537     /*clang-format off*/
538     if(event == LV_EVENT_PRESSED || event == LV_EVENT_PRESSING || event == LV_EVENT_PRESS_LOST ||
539        event == LV_EVENT_RELEASED || event == LV_EVENT_SHORT_CLICKED || event == LV_EVENT_CLICKED ||
540        event == LV_EVENT_LONG_PRESSED || event == LV_EVENT_LONG_PRESSED_REPEAT ||
541        event == LV_EVENT_VALUE_CHANGED) {
542         lv_event_send(mbox, event, lv_event_get_data());
543     }
544     /*clang-format on*/
545 }
546 
547 #endif
548