1 /**
2  * @file lv_calendar.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_calendar.h"
10 #if LV_USE_CALENDAR != 0
11 
12 #include "../lv_draw/lv_draw.h"
13 #include "../lv_hal/lv_hal_indev.h"
14 #include "../lv_misc/lv_utils.h"
15 #include "../lv_core/lv_indev.h"
16 #include "../lv_themes/lv_theme.h"
17 #include <string.h>
18 
19 /*********************
20  *      DEFINES
21  *********************/
22 
23 /**********************
24  *      TYPEDEFS
25  **********************/
26 enum {
27     DAY_DRAW_PREV_MONTH,
28     DAY_DRAW_ACT_MONTH,
29     DAY_DRAW_NEXT_MONTH,
30 };
31 typedef uint8_t day_draw_state_t;
32 
33 /**********************
34  *  STATIC PROTOTYPES
35  **********************/
36 static bool lv_calendar_design(lv_obj_t * calendar, const lv_area_t * mask, lv_design_mode_t mode);
37 static lv_res_t lv_calendar_signal(lv_obj_t * calendar, lv_signal_t sign, void * param);
38 static bool calculate_touched_day(lv_obj_t * calendar, const lv_point_t * touched_point);
39 static lv_coord_t get_header_height(lv_obj_t * calendar);
40 static lv_coord_t get_day_names_height(lv_obj_t * calendar);
41 static void draw_header(lv_obj_t * calendar, const lv_area_t * mask);
42 static void draw_day_names(lv_obj_t * calendar, const lv_area_t * mask);
43 static void draw_days(lv_obj_t * calendar, const lv_area_t * mask);
44 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day);
45 static bool is_highlighted(lv_obj_t * calendar, int32_t year, int32_t month, int32_t day);
46 static const char * get_day_name(lv_obj_t * calendar, uint8_t day);
47 static const char * get_month_name(lv_obj_t * calendar, int32_t month);
48 static uint8_t get_month_length(int32_t year, int32_t month);
49 static uint8_t is_leap_year(uint32_t year);
50 
51 /**********************
52  *  STATIC VARIABLES
53  **********************/
54 static lv_signal_cb_t ancestor_signal;
55 static lv_design_cb_t ancestor_design;
56 static const char * day_name[7]    = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};
57 static const char * month_name[12] = {"January", "February", "March",     "April",   "May",      "June",
58                                       "July",    "August",   "September", "October", "November", "December"};
59 
60 /**********************
61  *      MACROS
62  **********************/
63 
64 /**********************
65  *   GLOBAL FUNCTIONS
66  **********************/
67 
68 /**
69  * Create a calendar object
70  * @param par pointer to an object, it will be the parent of the new calendar
71  * @param copy pointer to a calendar object, if not NULL then the new object will be copied from it
72  * @return pointer to the created calendar
73  */
lv_calendar_create(lv_obj_t * par,const lv_obj_t * copy)74 lv_obj_t * lv_calendar_create(lv_obj_t * par, const lv_obj_t * copy)
75 {
76     LV_LOG_TRACE("calendar create started");
77 
78     /*Create the ancestor of calendar*/
79     lv_obj_t * new_calendar = lv_obj_create(par, copy);
80     lv_mem_assert(new_calendar);
81     if(new_calendar == NULL) return NULL;
82 
83     /*Allocate the calendar type specific extended data*/
84     lv_calendar_ext_t * ext = lv_obj_allocate_ext_attr(new_calendar, sizeof(lv_calendar_ext_t));
85     lv_mem_assert(ext);
86     if(ext == NULL) return NULL;
87     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(new_calendar);
88     if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(new_calendar);
89 
90     /*Initialize the allocated 'ext' */
91     ext->today.year  = 2018;
92     ext->today.month = 1;
93     ext->today.day   = 1;
94 
95     ext->showed_date.year  = 2018;
96     ext->showed_date.month = 1;
97     ext->showed_date.day   = 1;
98 
99     ext->pressed_date.year  = 0;
100     ext->pressed_date.month = 0;
101     ext->pressed_date.day   = 0;
102 
103     ext->highlighted_dates      = NULL;
104     ext->highlighted_dates_num  = 0;
105     ext->day_names              = NULL;
106     ext->month_names            = NULL;
107     ext->style_header           = &lv_style_plain_color;
108     ext->style_header_pr        = &lv_style_pretty_color;
109     ext->style_highlighted_days = &lv_style_plain_color;
110     ext->style_inactive_days    = &lv_style_btn_ina;
111     ext->style_week_box         = &lv_style_plain_color;
112     ext->style_today_box        = &lv_style_pretty_color;
113     ext->style_day_names        = &lv_style_pretty;
114 
115     /*The signal and design functions are not copied so set them here*/
116     lv_obj_set_signal_cb(new_calendar, lv_calendar_signal);
117     lv_obj_set_design_cb(new_calendar, lv_calendar_design);
118 
119     /*Init the new calendar calendar*/
120     if(copy == NULL) {
121         lv_obj_set_size(new_calendar, LV_DPI * 2, LV_DPI * 2);
122         lv_obj_set_style(new_calendar, &lv_style_pretty);
123 
124         lv_theme_t * th = lv_theme_get_current();
125         if(th) {
126             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_BG, th->style.calendar.bg);
127             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_HEADER, th->style.calendar.header);
128             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_HEADER_PR, th->style.calendar.header_pr);
129             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_DAY_NAMES, th->style.calendar.day_names);
130             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_WEEK_BOX, th->style.calendar.week_box);
131             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_TODAY_BOX, th->style.calendar.today_box);
132             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_HIGHLIGHTED_DAYS,
133                                   th->style.calendar.highlighted_days);
134             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_INACTIVE_DAYS, th->style.calendar.inactive_days);
135         } else {
136             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_BG, &lv_style_pretty);
137             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_HEADER, ext->style_header);
138             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_HEADER_PR, ext->style_header_pr);
139             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_DAY_NAMES, ext->style_day_names);
140             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_WEEK_BOX, ext->style_week_box);
141             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_TODAY_BOX, ext->style_today_box);
142             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_HIGHLIGHTED_DAYS, ext->style_highlighted_days);
143             lv_calendar_set_style(new_calendar, LV_CALENDAR_STYLE_INACTIVE_DAYS, ext->style_inactive_days);
144         }
145     }
146     /*Copy an existing calendar*/
147     else {
148         lv_calendar_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
149         ext->today.year              = copy_ext->today.year;
150         ext->today.month             = copy_ext->today.month;
151         ext->today.day               = copy_ext->today.day;
152 
153         ext->showed_date.year  = copy_ext->showed_date.year;
154         ext->showed_date.month = copy_ext->showed_date.month;
155         ext->showed_date.day   = copy_ext->showed_date.day;
156 
157         ext->highlighted_dates     = copy_ext->highlighted_dates;
158         ext->highlighted_dates_num = copy_ext->highlighted_dates_num;
159         ext->day_names             = copy_ext->day_names;
160 
161         ext->month_names            = copy_ext->month_names;
162         ext->style_header           = copy_ext->style_header;
163         ext->style_header_pr        = copy_ext->style_header_pr;
164         ext->style_highlighted_days = copy_ext->style_highlighted_days;
165         ext->style_inactive_days    = copy_ext->style_inactive_days;
166         ext->style_week_box         = copy_ext->style_week_box;
167         ext->style_today_box        = copy_ext->style_today_box;
168         ext->style_day_names        = copy_ext->style_day_names;
169         /*Refresh the style with new signal function*/
170         lv_obj_refresh_style(new_calendar);
171     }
172 
173     LV_LOG_INFO("calendar created");
174 
175     return new_calendar;
176 }
177 
178 /*======================
179  * Add/remove functions
180  *=====================*/
181 
182 /*
183  * New object specific "add" or "remove" functions come here
184  */
185 
186 /*=====================
187  * Setter functions
188  *====================*/
189 
190 /**
191  * Set the today's date
192  * @param calendar pointer to a calendar object
193  * @param today pointer to an `lv_calendar_date_t` variable containing the date of today. The value
194  * will be saved it can be local variable too.
195  */
lv_calendar_set_today_date(lv_obj_t * calendar,lv_calendar_date_t * today)196 void lv_calendar_set_today_date(lv_obj_t * calendar, lv_calendar_date_t * today)
197 {
198     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
199     ext->today.year         = today->year;
200     ext->today.month        = today->month;
201     ext->today.day          = today->day;
202 
203     lv_obj_invalidate(calendar);
204 }
205 
206 /**
207  * Set the currently showed
208  * @param calendar pointer to a calendar object
209  * @param showed pointer to an `lv_calendar_date_t` variable containing the date to show. The value
210  * will be saved it can be local variable too.
211  */
lv_calendar_set_showed_date(lv_obj_t * calendar,lv_calendar_date_t * showed)212 void lv_calendar_set_showed_date(lv_obj_t * calendar, lv_calendar_date_t * showed)
213 {
214     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
215     ext->showed_date.year   = showed->year;
216     ext->showed_date.month  = showed->month;
217     ext->showed_date.day    = showed->day;
218 
219     lv_obj_invalidate(calendar);
220 }
221 
222 /**
223  * Set the the highlighted dates
224  * @param calendar pointer to a calendar object
225  * @param highlighted pointer to an `lv_calendar_date_t` array containing the dates. ONLY A POINTER
226  * WILL BE SAVED! CAN'T BE LOCAL ARRAY.
227  * @param date_num number of dates in the array
228  */
lv_calendar_set_highlighted_dates(lv_obj_t * calendar,lv_calendar_date_t * highlighted,uint16_t date_num)229 void lv_calendar_set_highlighted_dates(lv_obj_t * calendar, lv_calendar_date_t * highlighted, uint16_t date_num)
230 {
231     lv_calendar_ext_t * ext    = lv_obj_get_ext_attr(calendar);
232     ext->highlighted_dates     = highlighted;
233     ext->highlighted_dates_num = date_num;
234 
235     lv_obj_invalidate(calendar);
236 }
237 
238 /**
239  * Set the name of the days
240  * @param calendar pointer to a calendar object
241  * @param day_names pointer to an array with the names. E.g. `const char * days[7] = {"Sun", "Mon",
242  * ...}` Only the pointer will be saved so this variable can't be local which will be destroyed
243  * later.
244  */
lv_calendar_set_day_names(lv_obj_t * calendar,const char ** day_names)245 void lv_calendar_set_day_names(lv_obj_t * calendar, const char ** day_names)
246 {
247     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
248     ext->day_names          = day_names;
249     lv_obj_invalidate(calendar);
250 }
251 
252 /**
253  * Set the name of the month
254  * @param calendar pointer to a calendar object
255  * @param day_names pointer to an array with the names. E.g. `const char * days[12] = {"Jan", "Feb",
256  * ...}` Only the pointer will be saved so this variable can't be local which will be destroyed
257  * later.
258  */
lv_calendar_set_month_names(lv_obj_t * calendar,const char ** day_names)259 void lv_calendar_set_month_names(lv_obj_t * calendar, const char ** day_names)
260 {
261     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
262     ext->month_names        = day_names;
263     lv_obj_invalidate(calendar);
264 }
265 
266 /**
267  * Set a style of a calendar.
268  * @param calendar pointer to calendar object
269  * @param type which style should be set
270  * @param style pointer to a style
271  *  */
lv_calendar_set_style(lv_obj_t * calendar,lv_calendar_style_t type,const lv_style_t * style)272 void lv_calendar_set_style(lv_obj_t * calendar, lv_calendar_style_t type, const lv_style_t * style)
273 {
274     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
275 
276     switch(type) {
277         case LV_CALENDAR_STYLE_BG: lv_obj_set_style(calendar, style); break;
278         case LV_CALENDAR_STYLE_DAY_NAMES: ext->style_day_names = style; break;
279         case LV_CALENDAR_STYLE_HEADER: ext->style_header = style; break;
280         case LV_CALENDAR_STYLE_HEADER_PR: ext->style_header_pr = style; break;
281         case LV_CALENDAR_STYLE_HIGHLIGHTED_DAYS: ext->style_highlighted_days = style; break;
282         case LV_CALENDAR_STYLE_INACTIVE_DAYS: ext->style_inactive_days = style; break;
283         case LV_CALENDAR_STYLE_TODAY_BOX: ext->style_today_box = style; break;
284         case LV_CALENDAR_STYLE_WEEK_BOX: ext->style_week_box = style; break;
285     }
286 
287     lv_obj_invalidate(calendar);
288 }
289 
290 /*=====================
291  * Getter functions
292  *====================*/
293 
294 /**
295  * Get the today's date
296  * @param calendar pointer to a calendar object
297  * @return return pointer to an `lv_calendar_date_t` variable containing the date of today.
298  */
lv_calendar_get_today_date(const lv_obj_t * calendar)299 lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * calendar)
300 {
301     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
302     return &ext->today;
303 }
304 
305 /**
306  * Get the currently showed
307  * @param calendar pointer to a calendar object
308  * @return pointer to an `lv_calendar_date_t` variable containing the date is being shown.
309  */
lv_calendar_get_showed_date(const lv_obj_t * calendar)310 lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * calendar)
311 {
312     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
313     return &ext->showed_date;
314 }
315 
316 /**
317  * Get the the pressed date.
318  * @param calendar pointer to a calendar object
319  * @return pointer to an `lv_calendar_date_t` variable containing the pressed date.
320  * `NULL` if not date pressed (e.g. the header)
321  */
lv_calendar_get_pressed_date(const lv_obj_t * calendar)322 lv_calendar_date_t * lv_calendar_get_pressed_date(const lv_obj_t * calendar)
323 {
324     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
325     return ext->pressed_date.year != 0 ? &ext->pressed_date : NULL;
326 }
327 
328 /**
329  * Get the the highlighted dates
330  * @param calendar pointer to a calendar object
331  * @return pointer to an `lv_calendar_date_t` array containing the dates.
332  */
lv_calendar_get_highlighted_dates(const lv_obj_t * calendar)333 lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * calendar)
334 {
335     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
336     return ext->highlighted_dates;
337 }
338 
339 /**
340  * Get the number of the highlighted dates
341  * @param calendar pointer to a calendar object
342  * @return number of highlighted days
343  */
lv_calendar_get_highlighted_dates_num(const lv_obj_t * calendar)344 uint16_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * calendar)
345 {
346     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
347     return ext->highlighted_dates_num;
348 }
349 
350 /**
351  * Get the name of the days
352  * @param calendar pointer to a calendar object
353  * @return pointer to the array of day names
354  */
lv_calendar_get_day_names(const lv_obj_t * calendar)355 const char ** lv_calendar_get_day_names(const lv_obj_t * calendar)
356 {
357     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
358     return ext->day_names;
359 }
360 
361 /**
362  * Get the name of the month
363  * @param calendar pointer to a calendar object
364  * @return pointer to the array of month names
365  */
lv_calendar_get_month_names(const lv_obj_t * calendar)366 const char ** lv_calendar_get_month_names(const lv_obj_t * calendar)
367 {
368     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
369     return ext->month_names;
370 }
371 
372 /**
373  * Get style of a calendar.
374  * @param calendar pointer to calendar object
375  * @param type which style should be get
376  * @return style pointer to the style
377  *  */
lv_calendar_get_style(const lv_obj_t * calendar,lv_calendar_style_t type)378 const lv_style_t * lv_calendar_get_style(const lv_obj_t * calendar, lv_calendar_style_t type)
379 {
380     const lv_style_t * style = NULL;
381     lv_calendar_ext_t * ext  = lv_obj_get_ext_attr(calendar);
382 
383     switch(type) {
384         case LV_CALENDAR_STYLE_BG: style = lv_obj_get_style(calendar); break;
385         case LV_CALENDAR_STYLE_HEADER: style = ext->style_header; break;
386         case LV_CALENDAR_STYLE_HEADER_PR: style = ext->style_header_pr; break;
387         case LV_CALENDAR_STYLE_DAY_NAMES: style = ext->style_day_names; break;
388         case LV_CALENDAR_STYLE_HIGHLIGHTED_DAYS: style = ext->style_highlighted_days; break;
389         case LV_CALENDAR_STYLE_INACTIVE_DAYS: style = ext->style_inactive_days; break;
390         case LV_CALENDAR_STYLE_WEEK_BOX: style = ext->style_week_box; break;
391         case LV_CALENDAR_STYLE_TODAY_BOX: style = ext->style_today_box; break;
392         default: style = NULL; break;
393     }
394 
395     return style;
396 }
397 
398 /*=====================
399  * Other functions
400  *====================*/
401 
402 /*
403  * New object specific "other" functions come here
404  */
405 
406 /**********************
407  *   STATIC FUNCTIONS
408  **********************/
409 
410 /**
411  * Handle the drawing related tasks of the calendars
412  * @param calendar pointer to an object
413  * @param mask the object will be drawn only in this area
414  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
415  *                                  (return 'true' if yes)
416  *             LV_DESIGN_DRAW: draw the object (always return 'true')
417  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
418  * @param return true/false, depends on 'mode'
419  */
lv_calendar_design(lv_obj_t * calendar,const lv_area_t * mask,lv_design_mode_t mode)420 static bool lv_calendar_design(lv_obj_t * calendar, const lv_area_t * mask, lv_design_mode_t mode)
421 {
422     /*Return false if the object is not covers the mask_p area*/
423     if(mode == LV_DESIGN_COVER_CHK) {
424         return ancestor_design(calendar, mask, mode);
425     }
426     /*Draw the object*/
427     else if(mode == LV_DESIGN_DRAW_MAIN) {
428         lv_opa_t opa_scale = lv_obj_get_opa_scale(calendar);
429         lv_draw_rect(&calendar->coords, mask, lv_calendar_get_style(calendar, LV_CALENDAR_STYLE_BG), opa_scale);
430 
431         draw_header(calendar, mask);
432         draw_day_names(calendar, mask);
433         draw_days(calendar, mask);
434 
435     }
436     /*Post draw when the children are drawn*/
437     else if(mode == LV_DESIGN_DRAW_POST) {
438     }
439 
440     return true;
441 }
442 
443 /**
444  * Signal function of the calendar
445  * @param calendar pointer to a calendar object
446  * @param sign a signal type from lv_signal_t enum
447  * @param param pointer to a signal specific variable
448  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
449  */
lv_calendar_signal(lv_obj_t * calendar,lv_signal_t sign,void * param)450 static lv_res_t lv_calendar_signal(lv_obj_t * calendar, lv_signal_t sign, void * param)
451 {
452     lv_res_t res;
453 
454     /* Include the ancient signal function */
455     res = ancestor_signal(calendar, sign, param);
456     if(res != LV_RES_OK) return res;
457 
458     if(sign == LV_SIGNAL_CLEANUP) {
459         /*Nothing to cleanup. (No dynamically allocated memory in 'ext')*/
460     } else if(sign == LV_SIGNAL_PRESSING) {
461         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
462         lv_area_t header_area;
463         lv_area_copy(&header_area, &calendar->coords);
464         header_area.y2 = header_area.y1 + get_header_height(calendar);
465 
466         lv_indev_t * indev = lv_indev_get_act();
467         lv_point_t p;
468         lv_indev_get_point(indev, &p);
469 
470         /*If the header is pressed mark an arrow as pressed*/
471         if(lv_area_is_point_on(&header_area, &p)) {
472             if(p.x < header_area.x1 + lv_area_get_width(&header_area) / 2) {
473                 if(ext->btn_pressing != -1) lv_obj_invalidate(calendar);
474                 ext->btn_pressing = -1;
475             } else {
476                 if(ext->btn_pressing != 1) lv_obj_invalidate(calendar);
477                 ext->btn_pressing = 1;
478             }
479 
480             ext->pressed_date.year  = 0;
481             ext->pressed_date.month = 0;
482             ext->pressed_date.day   = 0;
483         }
484         /*If a day is pressed save it*/
485         else if(calculate_touched_day(calendar, &p)) {
486             if(ext->btn_pressing != 0) lv_obj_invalidate(calendar);
487             ext->btn_pressing = 0;
488         }
489         /*ELse set a deafault state*/
490         else {
491             if(ext->btn_pressing != 0) lv_obj_invalidate(calendar);
492             ext->btn_pressing       = 0;
493             ext->pressed_date.year  = 0;
494             ext->pressed_date.month = 0;
495             ext->pressed_date.day   = 0;
496         }
497     } else if(sign == LV_SIGNAL_PRESS_LOST) {
498         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
499         ext->btn_pressing       = 0;
500         lv_obj_invalidate(calendar);
501 
502     } else if(sign == LV_SIGNAL_RELEASED) {
503         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
504         if(ext->btn_pressing < 0) {
505             if(ext->showed_date.month <= 1) {
506                 ext->showed_date.month = 12;
507                 ext->showed_date.year--;
508             } else {
509                 ext->showed_date.month--;
510             }
511         } else if(ext->btn_pressing > 0) {
512             if(ext->showed_date.month >= 12) {
513                 ext->showed_date.month = 1;
514                 ext->showed_date.year++;
515             } else {
516                 ext->showed_date.month++;
517             }
518         } else if(ext->pressed_date.year != 0) {
519             res = lv_event_send(calendar, LV_EVENT_VALUE_CHANGED, NULL);
520             if(res != LV_RES_OK) return res;
521         }
522 
523         ext->btn_pressing = 0;
524         lv_obj_invalidate(calendar);
525     } else if(sign == LV_SIGNAL_CONTROL) {
526         uint8_t c               = *((uint8_t *)param);
527         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
528         if(c == LV_KEY_RIGHT || c == LV_KEY_UP) {
529             if(ext->showed_date.month >= 12) {
530                 ext->showed_date.month = 1;
531                 ext->showed_date.year++;
532             } else {
533                 ext->showed_date.month++;
534             }
535             lv_obj_invalidate(calendar);
536         } else if(c == LV_KEY_LEFT || c == LV_KEY_DOWN) {
537             if(ext->showed_date.month <= 1) {
538                 ext->showed_date.month = 12;
539                 ext->showed_date.year--;
540             } else {
541                 ext->showed_date.month--;
542             }
543             lv_obj_invalidate(calendar);
544         }
545     } else if(sign == LV_SIGNAL_GET_TYPE) {
546         lv_obj_type_t * buf = param;
547         uint8_t i;
548         for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) { /*Find the last set date*/
549             if(buf->type[i] == NULL) break;
550         }
551         buf->type[i] = "lv_calendar";
552     }
553 
554     return res;
555 }
556 
557 /**
558  * It will check if the days part of calendar is touched
559  * and if it is, it will calculate the day and put it in pressed_date of calendar object.
560  * @param calendar pointer to a calendar object
561  * @param pointer to a point
562  * @return true: days part of calendar is touched and its related date is put in pressed date
563  * false: the point is out of days part area.
564  */
calculate_touched_day(lv_obj_t * calendar,const lv_point_t * touched_point)565 static bool calculate_touched_day(lv_obj_t * calendar, const lv_point_t * touched_point)
566 {
567     lv_area_t days_area;
568     lv_area_copy(&days_area, &calendar->coords);
569     const lv_style_t * style_bg = lv_calendar_get_style(calendar, LV_CALENDAR_STYLE_BG);
570     days_area.x1 += style_bg->body.padding.left;
571     days_area.x2 -= style_bg->body.padding.right;
572     days_area.y1 =
573         calendar->coords.y1 + get_header_height(calendar) + get_day_names_height(calendar) - style_bg->body.padding.top;
574 
575     if(lv_area_is_point_on(&days_area, touched_point)) {
576         lv_coord_t w  = (days_area.x2 - days_area.x1 + 1) / 7;
577         lv_coord_t h  = (days_area.y2 - days_area.y1 + 1) / 6;
578         uint8_t x_pos = 0;
579         x_pos         = (touched_point->x - days_area.x1) / w;
580         if(x_pos > 6) x_pos = 6;
581         uint8_t y_pos = 0;
582         y_pos         = (touched_point->y - days_area.y1) / h;
583         if(y_pos > 5) y_pos = 5;
584 
585         uint8_t i_pos           = 0;
586         i_pos                   = (y_pos * 7) + x_pos;
587         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
588         if(i_pos < get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1)) {
589             ext->pressed_date.year  = ext->showed_date.year - (ext->showed_date.month == 1 ? 1 : 0);
590             ext->pressed_date.month = ext->showed_date.month == 1 ? 12 : (ext->showed_date.month - 1);
591             ext->pressed_date.day   = get_month_length(ext->pressed_date.year, ext->pressed_date.month) -
592                                     get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1) + 1 + i_pos;
593         } else if(i_pos < (get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1) +
594                            get_month_length(ext->showed_date.year, ext->showed_date.month))) {
595             ext->pressed_date.year  = ext->showed_date.year;
596             ext->pressed_date.month = ext->showed_date.month;
597             ext->pressed_date.day   = i_pos + 1 - get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1);
598         } else if(i_pos < 42) {
599             ext->pressed_date.year  = ext->showed_date.year + (ext->showed_date.month == 12 ? 1 : 0);
600             ext->pressed_date.month = ext->showed_date.month == 12 ? 1 : (ext->showed_date.month + 1);
601             ext->pressed_date.day   = i_pos + 1 - get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1) -
602                                     get_month_length(ext->showed_date.year, ext->showed_date.month);
603         }
604         return true;
605     } else {
606         return false;
607     }
608 }
609 
610 /**
611  * Get the height of a calendar's header based on it's style
612  * @param calendar point to a calendar
613  * @return the header's height
614  */
get_header_height(lv_obj_t * calendar)615 static lv_coord_t get_header_height(lv_obj_t * calendar)
616 {
617     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
618 
619     return lv_font_get_line_height(ext->style_header->text.font) + ext->style_header->body.padding.top +
620            ext->style_header->body.padding.bottom;
621 }
622 
623 /**
624  * Get the height of a calendar's day_names based on it's style
625  * @param calendar point to a calendar
626  * @return the day_names's height
627  */
get_day_names_height(lv_obj_t * calendar)628 static lv_coord_t get_day_names_height(lv_obj_t * calendar)
629 {
630     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
631 
632     return lv_font_get_line_height(ext->style_day_names->text.font) + ext->style_day_names->body.padding.top +
633            ext->style_day_names->body.padding.bottom;
634 }
635 
636 /**
637  * Draw the calendar header with month name and arrows
638  * @param calendar point to a calendar
639  * @param mask a mask for drawing
640  */
draw_header(lv_obj_t * calendar,const lv_area_t * mask)641 static void draw_header(lv_obj_t * calendar, const lv_area_t * mask)
642 {
643     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
644     lv_opa_t opa_scale      = lv_obj_get_opa_scale(calendar);
645 
646     lv_area_t header_area;
647     header_area.x1 = calendar->coords.x1;
648     header_area.x2 = calendar->coords.x2;
649     header_area.y1 = calendar->coords.y1;
650     header_area.y2 = calendar->coords.y1 + get_header_height(calendar);
651 
652     lv_draw_rect(&header_area, mask, ext->style_header, opa_scale);
653 
654     /*Add the year + month name*/
655     char txt_buf[64];
656     lv_utils_num_to_str(ext->showed_date.year, txt_buf);
657     txt_buf[4] = ' ';
658     txt_buf[5] = '\0';
659     strcpy(&txt_buf[5], get_month_name(calendar, ext->showed_date.month));
660     header_area.y1 += ext->style_header->body.padding.top;
661     lv_draw_label(&header_area, mask, ext->style_header, opa_scale, txt_buf, LV_TXT_FLAG_CENTER, NULL, -1, -1, NULL);
662 
663     /*Add the left arrow*/
664     const lv_style_t * arrow_style = ext->btn_pressing < 0 ? ext->style_header_pr : ext->style_header;
665     header_area.x1 += ext->style_header->body.padding.left;
666     lv_draw_label(&header_area, mask, arrow_style, opa_scale, LV_SYMBOL_LEFT, LV_TXT_FLAG_NONE, NULL, -1, -1, NULL);
667 
668     /*Add the right arrow*/
669     arrow_style    = ext->btn_pressing > 0 ? ext->style_header_pr : ext->style_header;
670     header_area.x1 = header_area.x2 - ext->style_header->body.padding.right -
671                      lv_txt_get_width(LV_SYMBOL_RIGHT, strlen(LV_SYMBOL_RIGHT), arrow_style->text.font,
672                                       arrow_style->text.line_space, LV_TXT_FLAG_NONE);
673     lv_draw_label(&header_area, mask, arrow_style, opa_scale, LV_SYMBOL_RIGHT, LV_TXT_FLAG_NONE, NULL, -1, -1, NULL);
674 }
675 
676 /**
677  * Draw the day's name below the header
678  * @param calendar point to a calendar
679  * @param mask a mask for drawing
680  */
draw_day_names(lv_obj_t * calendar,const lv_area_t * mask)681 static void draw_day_names(lv_obj_t * calendar, const lv_area_t * mask)
682 {
683     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
684     lv_opa_t opa_scale      = lv_obj_get_opa_scale(calendar);
685 
686     lv_coord_t l_pad = ext->style_day_names->body.padding.left;
687     lv_coord_t w =
688         lv_obj_get_width(calendar) - ext->style_day_names->body.padding.left - ext->style_day_names->body.padding.right;
689     lv_coord_t box_w = w / 7;
690     lv_area_t label_area;
691     label_area.y1 = calendar->coords.y1 + get_header_height(calendar) + ext->style_day_names->body.padding.top;
692     label_area.y2 = label_area.y1 + lv_font_get_line_height(ext->style_day_names->text.font);
693     uint32_t i;
694     for(i = 0; i < 7; i++) {
695         label_area.x1 = calendar->coords.x1 + (w * i) / 7 + l_pad;
696         label_area.x2 = label_area.x1 + box_w - 1;
697         lv_draw_label(&label_area, mask, ext->style_day_names, opa_scale, get_day_name(calendar, i), LV_TXT_FLAG_CENTER,
698                       NULL, -1, -1, NULL);
699     }
700 }
701 
702 /**
703  * Draw the date numbers in a matrix
704  * @param calendar point to a calendar
705  * @param mask a mask for drawing
706  */
draw_days(lv_obj_t * calendar,const lv_area_t * mask)707 static void draw_days(lv_obj_t * calendar, const lv_area_t * mask)
708 {
709     lv_calendar_ext_t * ext     = lv_obj_get_ext_attr(calendar);
710     const lv_style_t * style_bg = lv_calendar_get_style(calendar, LV_CALENDAR_STYLE_BG);
711     lv_area_t label_area;
712     lv_opa_t opa_scale = lv_obj_get_opa_scale(calendar);
713     label_area.y1      = calendar->coords.y1 + get_header_height(calendar) + ext->style_day_names->body.padding.top +
714                     lv_font_get_line_height(ext->style_day_names->text.font) +
715                     ext->style_day_names->body.padding.bottom;
716     label_area.y2 = label_area.y1 + lv_font_get_line_height(style_bg->text.font);
717 
718     lv_coord_t w          = lv_obj_get_width(calendar) - style_bg->body.padding.left - style_bg->body.padding.right;
719     lv_coord_t h          = calendar->coords.y2 - label_area.y1 - style_bg->body.padding.bottom;
720     lv_coord_t box_w      = w / 7;
721     lv_coord_t vert_space = (h - (6 * lv_font_get_line_height(style_bg->text.font))) / 5;
722 
723     uint32_t week;
724     uint8_t day_cnt;
725     uint8_t month_start_day = get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1);
726     day_draw_state_t draw_state; /*true: Not the prev. or next month is drawn*/
727     const lv_style_t * act_style;
728 
729     /*If starting with the first day of the week then the previous month is not visible*/
730     if(month_start_day == 0) {
731         day_cnt    = 1;
732         draw_state = DAY_DRAW_ACT_MONTH;
733         act_style  = style_bg;
734     } else {
735         draw_state = DAY_DRAW_PREV_MONTH;
736         day_cnt = get_month_length(ext->showed_date.year, ext->showed_date.month - 1); /*Length of the previous month*/
737         day_cnt -= month_start_day - 1; /*First visible number of the previous month*/
738         act_style = ext->style_inactive_days;
739     }
740 
741     bool month_of_today_shown = false;
742     if(ext->showed_date.year == ext->today.year && ext->showed_date.month == ext->today.month) {
743         month_of_today_shown = true;
744     }
745 
746     char buf[3];
747     bool in_week_box = false;
748 
749     /*Draw 6 weeks*/
750     for(week = 0; week < 6; week++) {
751 
752         /*Draw the "week box"*/
753         if(month_of_today_shown &&
754            ((draw_state == DAY_DRAW_ACT_MONTH && ext->today.day >= day_cnt && ext->today.day < day_cnt + 7) ||
755             (draw_state == DAY_DRAW_PREV_MONTH && ext->today.day <= 7 - month_start_day && week == 0))) {
756             lv_area_t week_box_area;
757             lv_area_copy(&week_box_area, &label_area); /*'label_area' is already set for this row*/
758             week_box_area.x1 =
759                 calendar->coords.x1 + style_bg->body.padding.left - ext->style_week_box->body.padding.left;
760             week_box_area.x2 =
761                 calendar->coords.x2 - style_bg->body.padding.right + ext->style_week_box->body.padding.right;
762 
763             week_box_area.y1 -= ext->style_week_box->body.padding.top;
764             week_box_area.y2 += ext->style_week_box->body.padding.bottom;
765             lv_draw_rect(&week_box_area, mask, ext->style_week_box, opa_scale);
766 
767             in_week_box = true;
768         } else {
769             in_week_box = false;
770         }
771 
772         /*Draw the 7 days of a week*/
773         uint32_t day;
774         for(day = 0; day < 7; day++) {
775             /*The previous month is over*/
776             if(draw_state == DAY_DRAW_PREV_MONTH && day == month_start_day) {
777                 draw_state = DAY_DRAW_ACT_MONTH;
778                 day_cnt    = 1;
779                 act_style  = style_bg;
780             }
781             /*The current month is over*/
782             if(draw_state == DAY_DRAW_ACT_MONTH &&
783                day_cnt > get_month_length(ext->showed_date.year, ext->showed_date.month)) {
784                 draw_state = DAY_DRAW_NEXT_MONTH;
785                 day_cnt    = 1;
786                 act_style  = ext->style_inactive_days;
787             }
788 
789             label_area.x1 =
790                 calendar->coords.x1 + (w * day) / 7 + style_bg->body.padding.left;
791             label_area.x2 = label_area.x1 + box_w - 1;
792 
793             /*Draw the "today box"*/
794             if(draw_state == DAY_DRAW_ACT_MONTH && month_of_today_shown && ext->today.day == day_cnt) {
795                 lv_area_t today_box_area;
796                 lv_area_copy(&today_box_area, &label_area);
797                 today_box_area.x1 = label_area.x1;
798                 today_box_area.x2 = label_area.x2;
799 
800                 today_box_area.y1 = label_area.y1 - ext->style_today_box->body.padding.top;
801                 today_box_area.y2 = label_area.y2 + ext->style_today_box->body.padding.bottom;
802                 lv_draw_rect(&today_box_area, mask, ext->style_today_box, opa_scale);
803             }
804 
805             /*Get the final style : highlighted/week box/today box/normal*/
806             const lv_style_t * final_style;
807             if(draw_state == DAY_DRAW_PREV_MONTH &&
808                is_highlighted(calendar, ext->showed_date.year - (ext->showed_date.month == 1 ? 1 : 0),
809                               ext->showed_date.month == 1 ? 12 : ext->showed_date.month - 1, day_cnt)) {
810                 final_style = ext->style_highlighted_days;
811             } else if(draw_state == DAY_DRAW_ACT_MONTH &&
812                       is_highlighted(calendar, ext->showed_date.year, ext->showed_date.month, day_cnt)) {
813                 final_style = ext->style_highlighted_days;
814             } else if(draw_state == DAY_DRAW_NEXT_MONTH &&
815                       is_highlighted(calendar, ext->showed_date.year + (ext->showed_date.month == 12 ? 1 : 0),
816                                      ext->showed_date.month == 12 ? 1 : ext->showed_date.month + 1, day_cnt)) {
817                 final_style = ext->style_highlighted_days;
818             } else if(month_of_today_shown && day_cnt == ext->today.day && draw_state == DAY_DRAW_ACT_MONTH)
819                 final_style = ext->style_today_box;
820             else if(in_week_box && draw_state == DAY_DRAW_ACT_MONTH)
821                 final_style = ext->style_week_box;
822             else
823                 final_style = act_style;
824 
825             /*Write the day's number*/
826             lv_utils_num_to_str(day_cnt, buf);
827             lv_draw_label(&label_area, mask, final_style, opa_scale, buf, LV_TXT_FLAG_CENTER, NULL, -1, -1, NULL);
828 
829             /*Go to the next day*/
830             day_cnt++;
831         }
832 
833         /*Got to the next weeks row*/
834         label_area.y1 += vert_space + lv_font_get_line_height(style_bg->text.font);
835         label_area.y2 += vert_space + lv_font_get_line_height(style_bg->text.font);
836     }
837 }
838 
839 /**
840  * Check weather a date is highlighted or not
841  * @param calendar pointer to a calendar object
842  * @param year a year
843  * @param month a  month [1..12]
844  * @param day a day [1..31]
845  * @return true: highlighted
846  */
is_highlighted(lv_obj_t * calendar,int32_t year,int32_t month,int32_t day)847 static bool is_highlighted(lv_obj_t * calendar, int32_t year, int32_t month, int32_t day)
848 {
849     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
850 
851     if(ext->highlighted_dates == NULL || ext->highlighted_dates_num == 0) return false;
852 
853     uint32_t i;
854     for(i = 0; i < ext->highlighted_dates_num; i++) {
855         if(ext->highlighted_dates[i].year == year && ext->highlighted_dates[i].month == month &&
856            ext->highlighted_dates[i].day == day) {
857             return true;
858         }
859     }
860 
861     return false;
862 }
863 
864 /**
865  * Get the day name
866  * @param calendar pointer to a calendar object
867  * @param day a day in [0..6]
868  * @return
869  */
get_day_name(lv_obj_t * calendar,uint8_t day)870 static const char * get_day_name(lv_obj_t * calendar, uint8_t day)
871 {
872 
873     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
874     if(ext->day_names)
875         return ext->day_names[day];
876     else
877         return day_name[day];
878 }
879 
880 /**
881  * Get the month name
882  * @param calendar pointer to a calendar object
883  * @param month a month. The range is basically [1..12] but [-11..1] is also supported to handle
884  * previous year
885  * @return
886  */
get_month_name(lv_obj_t * calendar,int32_t month)887 static const char * get_month_name(lv_obj_t * calendar, int32_t month)
888 {
889     month--; /*Range of months id [1..12] but range of indexes is [0..11]*/
890     if(month < 0) month = 12 + month;
891 
892     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
893     if(ext->month_names)
894         return ext->month_names[month];
895     else
896         return month_name[month];
897 }
898 
899 /**
900  * Get the number of days in a month
901  * @param year a year
902  * @param month a month. The range is basically [1..12] but [-11..1] is also supported to handle
903  * previous year
904  * @return [28..31]
905  */
get_month_length(int32_t year,int32_t month)906 static uint8_t get_month_length(int32_t year, int32_t month)
907 {
908     month--; /*Range of months id [1..12] but range of indexes is [0..11]*/
909     if(month < 0) {
910         year--;             /*Already in the previous year (won't be less then -12 to skip a whole year)*/
911         month = 12 + month; /*`month` is negative, the result will be < 12*/
912     }
913     if(month >= 12) {
914         year++;
915         month -= 12;
916     }
917 
918     /*month == 1 is february*/
919     return (month == 1) ? (28 + is_leap_year(year)) : 31 - month % 7 % 2;
920 }
921 
922 /**
923  * Tells whether a year is leap year or not
924  * @param year a year
925  * @return 0: not leap year; 1: leap year
926  */
is_leap_year(uint32_t year)927 static uint8_t is_leap_year(uint32_t year)
928 {
929     return (year % 4) || ((year % 100 == 0) && (year % 400)) ? 0 : 1;
930 }
931 
932 /**
933  * Get the day of the week
934  * @param year a year
935  * @param month a  month
936  * @param day a day
937  * @return [0..6] which means [Sun..Sat]
938  */
get_day_of_week(uint32_t year,uint32_t month,uint32_t day)939 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day)
940 {
941     uint32_t a = month < 3 ? 1 : 0;
942     uint32_t b = year - a;
943 
944     uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400)) % 7;
945 
946     return day_of_week;
947 }
948 
949 #endif
950