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_misc/lv_debug.h"
13 #include "../lv_draw/lv_draw.h"
14 #include "../lv_hal/lv_hal_indev.h"
15 #include "../lv_misc/lv_utils.h"
16 #include "../lv_core/lv_indev.h"
17 #include "../lv_themes/lv_theme.h"
18 #include <string.h>
19 
20 /*********************
21  *      DEFINES
22  *********************/
23 #define LV_OBJX_NAME "lv_calendar"
24 
25 /**********************
26  *      TYPEDEFS
27  **********************/
28 enum {
29     DAY_DRAW_PREV_MONTH,
30     DAY_DRAW_ACT_MONTH,
31     DAY_DRAW_NEXT_MONTH,
32 };
33 typedef uint8_t day_draw_state_t;
34 
35 /**********************
36  *  STATIC PROTOTYPES
37  **********************/
38 static lv_design_res_t lv_calendar_design(lv_obj_t * calendar, const lv_area_t * clip_area, lv_design_mode_t mode);
39 static lv_res_t lv_calendar_signal(lv_obj_t * calendar, lv_signal_t sign, void * param);
40 static lv_style_list_t * lv_calendar_get_style(lv_obj_t * calendar, uint8_t part);
41 static bool calculate_touched_day(lv_obj_t * calendar, const lv_point_t * touched_point);
42 static lv_coord_t get_header_height(lv_obj_t * calendar);
43 static lv_coord_t get_day_names_height(lv_obj_t * calendar);
44 static void draw_header(lv_obj_t * calendar, const lv_area_t * mask);
45 static void draw_day_names(lv_obj_t * calendar, const lv_area_t * mask);
46 static void draw_dates(lv_obj_t * calendar, const lv_area_t * clip_area);
47 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day);
48 static bool is_highlighted(lv_obj_t * calendar, day_draw_state_t draw_state, int32_t year, int32_t month, int32_t day);
49 static bool is_pressed(lv_obj_t * calendar, day_draw_state_t draw_state, int32_t year, int32_t month, int32_t day);
50 static const char * get_day_name(lv_obj_t * calendar, uint8_t day);
51 static const char * get_month_name(lv_obj_t * calendar, int32_t month);
52 static uint8_t get_month_length(int32_t year, int32_t month);
53 static uint8_t is_leap_year(uint32_t year);
54 
55 /**********************
56  *  STATIC VARIABLES
57  **********************/
58 static lv_signal_cb_t ancestor_signal;
59 static lv_design_cb_t ancestor_design;
60 #if LV_CALENDAR_WEEK_STARTS_MONDAY != 0
61 static const char * day_name[7]    = {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"};
62 #else
63 static const char * day_name[7]    = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};
64 #endif
65 static const char * month_name[12] = {"January", "February", "March",     "April",   "May",      "June",
66                                       "July",    "August",   "September", "October", "November", "December"
67                                      };
68 
69 /**********************
70  *      MACROS
71  **********************/
72 
73 /**********************
74  *   GLOBAL FUNCTIONS
75  **********************/
76 
77 /**
78  * Create a calendar object
79  * @param par pointer to an object, it will be the parent of the new calendar
80  * @param copy pointer to a calendar object, if not NULL then the new object will be copied from it
81  * @return pointer to the created calendar
82  */
lv_calendar_create(lv_obj_t * par,const lv_obj_t * copy)83 lv_obj_t * lv_calendar_create(lv_obj_t * par, const lv_obj_t * copy)
84 {
85     LV_LOG_TRACE("calendar create started");
86 
87     /*Create the ancestor of calendar*/
88     lv_obj_t * calendar = lv_obj_create(par, copy);
89     LV_ASSERT_MEM(calendar);
90     if(calendar == NULL) return NULL;
91 
92     /*Allocate the calendar type specific extended data*/
93     lv_calendar_ext_t * ext = lv_obj_allocate_ext_attr(calendar, sizeof(lv_calendar_ext_t));
94     LV_ASSERT_MEM(ext);
95     if(ext == NULL) {
96         lv_obj_del(calendar);
97         return NULL;
98     }
99 
100     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(calendar);
101     if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(calendar);
102 
103     /*Initialize the allocated 'ext' */
104     ext->today.year  = 2020;
105     ext->today.month = 1;
106     ext->today.day   = 1;
107 
108     ext->showed_date.year  = 2020;
109     ext->showed_date.month = 1;
110     ext->showed_date.day   = 1;
111 
112     ext->pressed_date.year  = 0;
113     ext->pressed_date.month = 0;
114     ext->pressed_date.day   = 0;
115 
116     ext->highlighted_dates      = NULL;
117     ext->highlighted_dates_num  = 0;
118     ext->day_names              = NULL;
119     ext->month_names            = NULL;
120 
121     ext->btn_pressing = 0;
122 
123 
124     lv_style_list_init(&ext->style_date_nums);
125     lv_style_list_init(&ext->style_day_names);
126     lv_style_list_init(&ext->style_header);
127     ext->style_date_nums.skip_trans = 1;
128     ext->style_day_names.skip_trans = 1;
129     ext->style_header.skip_trans = 1;
130 
131     /*The signal and design functions are not copied so set them here*/
132     lv_obj_set_signal_cb(calendar, lv_calendar_signal);
133     lv_obj_set_design_cb(calendar, lv_calendar_design);
134 
135     /*Init the new calendar calendar*/
136     if(copy == NULL) {
137         lv_theme_apply(calendar, LV_THEME_CALENDAR);
138 
139         lv_obj_set_size(calendar, 5 * LV_DPI / 2, 5 * LV_DPI / 2);
140 
141     }
142     /*Copy an existing calendar*/
143     else {
144         lv_calendar_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
145         ext->today.year              = copy_ext->today.year;
146         ext->today.month             = copy_ext->today.month;
147         ext->today.day               = copy_ext->today.day;
148 
149         ext->showed_date.year  = copy_ext->showed_date.year;
150         ext->showed_date.month = copy_ext->showed_date.month;
151         ext->showed_date.day   = copy_ext->showed_date.day;
152 
153         ext->highlighted_dates     = copy_ext->highlighted_dates;
154         ext->highlighted_dates_num = copy_ext->highlighted_dates_num;
155         ext->day_names             = copy_ext->day_names;
156 
157         ext->month_names            = copy_ext->month_names;
158         ext->style_header           = copy_ext->style_header;
159         ext->style_day_names        = copy_ext->style_day_names;
160         /*Refresh the style with new signal function*/
161         //        lv_obj_refresh_style(new_calendar);
162     }
163 
164     LV_LOG_INFO("calendar created");
165 
166     return calendar;
167 }
168 
169 /*======================
170  * Add/remove functions
171  *=====================*/
172 
173 /*
174  * New object specific "add" or "remove" functions come here
175  */
176 
177 /*=====================
178  * Setter functions
179  *====================*/
180 
181 /**
182  * Set the today's date
183  * @param calendar pointer to a calendar object
184  * @param today pointer to an `lv_calendar_date_t` variable containing the date of today. The value
185  * will be saved it can be local variable too.
186  */
lv_calendar_set_today_date(lv_obj_t * calendar,lv_calendar_date_t * today)187 void lv_calendar_set_today_date(lv_obj_t * calendar, lv_calendar_date_t * today)
188 {
189     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
190     LV_ASSERT_NULL(today);
191 
192     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
193     ext->today.year         = today->year;
194     ext->today.month        = today->month;
195     ext->today.day          = today->day;
196 
197     lv_obj_invalidate(calendar);
198 }
199 
200 /**
201  * Set the currently showed
202  * @param calendar pointer to a calendar object
203  * @param showed pointer to an `lv_calendar_date_t` variable containing the date to show. The value
204  * will be saved it can be local variable too.
205  */
lv_calendar_set_showed_date(lv_obj_t * calendar,lv_calendar_date_t * showed)206 void lv_calendar_set_showed_date(lv_obj_t * calendar, lv_calendar_date_t * showed)
207 {
208     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
209     LV_ASSERT_NULL(showed);
210 
211     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
212     ext->showed_date.year   = showed->year;
213     ext->showed_date.month  = showed->month;
214     ext->showed_date.day    = showed->day;
215 
216     lv_obj_invalidate(calendar);
217 }
218 
219 /**
220  * Set the the highlighted dates
221  * @param calendar pointer to a calendar object
222  * @param highlighted pointer to an `lv_calendar_date_t` array containing the dates. ONLY A POINTER
223  * WILL BE SAVED! CAN'T BE LOCAL ARRAY.
224  * @param date_num number of dates in the array
225  */
lv_calendar_set_highlighted_dates(lv_obj_t * calendar,lv_calendar_date_t highlighted[],uint16_t date_num)226 void lv_calendar_set_highlighted_dates(lv_obj_t * calendar, lv_calendar_date_t highlighted[], uint16_t date_num)
227 {
228     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
229     LV_ASSERT_NULL(highlighted);
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_ASSERT_OBJ(calendar, LV_OBJX_NAME);
248     LV_ASSERT_NULL(day_names);
249 
250     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
251     ext->day_names          = day_names;
252     lv_obj_invalidate(calendar);
253 }
254 
255 /**
256  * Set the name of the month
257  * @param calendar pointer to a calendar object
258  * @param month_names pointer to an array with the names. E.g. `const char * days[12] = {"Jan", "Feb",
259  * ...}` Only the pointer will be saved so this variable can't be local which will be destroyed
260  * later.
261  */
lv_calendar_set_month_names(lv_obj_t * calendar,const char ** month_names)262 void lv_calendar_set_month_names(lv_obj_t * calendar, const char ** month_names)
263 {
264     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
265     LV_ASSERT_NULL(month_names);
266 
267     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
268     ext->month_names        = month_names;
269     lv_obj_invalidate(calendar);
270 }
271 
272 /*=====================
273  * Getter functions
274  *====================*/
275 
276 /**
277  * Get the today's date
278  * @param calendar pointer to a calendar object
279  * @return return pointer to an `lv_calendar_date_t` variable containing the date of today.
280  */
lv_calendar_get_today_date(const lv_obj_t * calendar)281 lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * calendar)
282 {
283     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
284 
285     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
286     return &ext->today;
287 }
288 
289 /**
290  * Get the currently showed
291  * @param calendar pointer to a calendar object
292  * @return pointer to an `lv_calendar_date_t` variable containing the date is being shown.
293  */
lv_calendar_get_showed_date(const lv_obj_t * calendar)294 lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * calendar)
295 {
296     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
297 
298     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
299     return &ext->showed_date;
300 }
301 
302 /**
303  * Get the the pressed date.
304  * @param calendar pointer to a calendar object
305  * @return pointer to an `lv_calendar_date_t` variable containing the pressed date.
306  * `NULL` if not date pressed (e.g. the header)
307  */
lv_calendar_get_pressed_date(const lv_obj_t * calendar)308 lv_calendar_date_t * lv_calendar_get_pressed_date(const lv_obj_t * calendar)
309 {
310     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
311 
312     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
313     return ext->pressed_date.year != 0 ? &ext->pressed_date : NULL;
314 }
315 
316 /**
317  * Get the the highlighted dates
318  * @param calendar pointer to a calendar object
319  * @return pointer to an `lv_calendar_date_t` array containing the dates.
320  */
lv_calendar_get_highlighted_dates(const lv_obj_t * calendar)321 lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * calendar)
322 {
323     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
324 
325     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
326     return ext->highlighted_dates;
327 }
328 
329 /**
330  * Get the number of the highlighted dates
331  * @param calendar pointer to a calendar object
332  * @return number of highlighted days
333  */
lv_calendar_get_highlighted_dates_num(const lv_obj_t * calendar)334 uint16_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * calendar)
335 {
336     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
337 
338     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
339     return ext->highlighted_dates_num;
340 }
341 
342 /**
343  * Get the name of the days
344  * @param calendar pointer to a calendar object
345  * @return pointer to the array of day names
346  */
lv_calendar_get_day_names(const lv_obj_t * calendar)347 const char ** lv_calendar_get_day_names(const lv_obj_t * calendar)
348 {
349     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
350 
351     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
352     return ext->day_names;
353 }
354 
355 /**
356  * Get the name of the month
357  * @param calendar pointer to a calendar object
358  * @return pointer to the array of month names
359  */
lv_calendar_get_month_names(const lv_obj_t * calendar)360 const char ** lv_calendar_get_month_names(const lv_obj_t * calendar)
361 {
362     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
363 
364     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
365     return ext->month_names;
366 }
367 
368 /*=====================
369  * Other functions
370  *====================*/
371 
372 /*
373  * New object specific "other" functions come here
374  */
375 
376 /**********************
377  *   STATIC FUNCTIONS
378  **********************/
379 
380 /**
381  * Handle the drawing related tasks of the calendars
382  * @param calendar pointer to an object
383  * @param clip_area the object will be drawn only in this area
384  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
385  *                                  (return 'true' if yes)
386  *             LV_DESIGN_DRAW: draw the object (always return 'true')
387  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
388  * @param return an element of `lv_design_res_t`
389  */
lv_calendar_design(lv_obj_t * calendar,const lv_area_t * clip_area,lv_design_mode_t mode)390 static lv_design_res_t lv_calendar_design(lv_obj_t * calendar, const lv_area_t * clip_area, lv_design_mode_t mode)
391 {
392     /*Return false if the object is not covers the mask_p area*/
393     if(mode == LV_DESIGN_COVER_CHK) {
394         return ancestor_design(calendar, clip_area, mode);
395     }
396     /*Draw the object*/
397     else if(mode == LV_DESIGN_DRAW_MAIN) {
398         ancestor_design(calendar, clip_area, mode);
399 
400         draw_header(calendar, clip_area);
401         draw_day_names(calendar, clip_area);
402         draw_dates(calendar, clip_area);
403 
404     }
405     /*Post draw when the children are drawn*/
406     else if(mode == LV_DESIGN_DRAW_POST) {
407         ancestor_design(calendar, clip_area, mode);
408     }
409 
410     return LV_DESIGN_RES_OK;
411 }
412 
413 /**
414  * Signal function of the calendar
415  * @param calendar pointer to a calendar object
416  * @param sign a signal type from lv_signal_t enum
417  * @param param pointer to a signal specific variable
418  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
419  */
lv_calendar_signal(lv_obj_t * calendar,lv_signal_t sign,void * param)420 static lv_res_t lv_calendar_signal(lv_obj_t * calendar, lv_signal_t sign, void * param)
421 {
422     lv_res_t res;
423     if(sign == LV_SIGNAL_GET_STYLE) {
424         lv_get_style_info_t * info = param;
425         info->result = lv_calendar_get_style(calendar, info->part);
426         if(info->result != NULL) return LV_RES_OK;
427         else return ancestor_signal(calendar, sign, param);
428         return LV_RES_OK;
429     }
430 
431     /* Include the ancient signal function */
432     res = ancestor_signal(calendar, sign, param);
433     if(res != LV_RES_OK) return res;
434     if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
435 
436     if(sign == LV_SIGNAL_CLEANUP) {
437         lv_obj_clean_style_list(calendar, LV_CALENDAR_PART_HEADER);
438         lv_obj_clean_style_list(calendar, LV_CALENDAR_PART_DAY_NAMES);
439         lv_obj_clean_style_list(calendar, LV_CALENDAR_PART_DATE);
440     }
441     else if(sign == LV_SIGNAL_PRESSING) {
442         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
443         lv_area_t header_area;
444         lv_area_copy(&header_area, &calendar->coords);
445         header_area.y2 = header_area.y1 + get_header_height(calendar);
446 
447         lv_indev_t * indev = lv_indev_get_act();
448         lv_point_t p;
449         lv_indev_get_point(indev, &p);
450 
451         /*If the header is pressed mark an arrow as pressed*/
452         if(_lv_area_is_point_on(&header_area, &p, 0)) {
453             if(p.x < header_area.x1 + lv_area_get_width(&header_area) / 2) {
454                 if(ext->btn_pressing != -1) lv_obj_invalidate(calendar);
455                 ext->btn_pressing = -1;
456             }
457             else {
458                 if(ext->btn_pressing != 1) lv_obj_invalidate(calendar);
459                 ext->btn_pressing = 1;
460             }
461 
462             ext->pressed_date.year  = 0;
463             ext->pressed_date.month = 0;
464             ext->pressed_date.day   = 0;
465         }
466         /*If a day is pressed save it*/
467         else if(calculate_touched_day(calendar, &p)) {
468             ext->btn_pressing = 0;
469             lv_obj_invalidate(calendar);
470         }
471         /*Else set a default state*/
472         else {
473             if(ext->btn_pressing != 0) lv_obj_invalidate(calendar);
474             ext->btn_pressing       = 0;
475             ext->pressed_date.year  = 0;
476             ext->pressed_date.month = 0;
477             ext->pressed_date.day   = 0;
478         }
479     }
480     else if(sign == LV_SIGNAL_PRESS_LOST) {
481         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
482         ext->btn_pressing       = 0;
483         ext->pressed_date.year  = 0;
484         ext->pressed_date.month = 0;
485         ext->pressed_date.day   = 0;
486         lv_obj_invalidate(calendar);
487 
488     }
489     else if(sign == LV_SIGNAL_RELEASED) {
490         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
491         if(ext->btn_pressing < 0) {
492             if(ext->showed_date.month <= 1) {
493                 ext->showed_date.month = 12;
494                 ext->showed_date.year--;
495             }
496             else {
497                 ext->showed_date.month--;
498             }
499         }
500         else if(ext->btn_pressing > 0) {
501             if(ext->showed_date.month >= 12) {
502                 ext->showed_date.month = 1;
503                 ext->showed_date.year++;
504             }
505             else {
506                 ext->showed_date.month++;
507             }
508         }
509         else if(ext->pressed_date.year != 0) {
510             res = lv_event_send(calendar, LV_EVENT_VALUE_CHANGED, NULL);
511             if(res != LV_RES_OK) return res;
512 
513         }
514 
515         ext->btn_pressing = 0;
516         ext->pressed_date.year  = 0;
517         ext->pressed_date.month = 0;
518         ext->pressed_date.day   = 0;
519         lv_obj_invalidate(calendar);
520     }
521     else if(sign == LV_SIGNAL_CONTROL) {
522 #if LV_USE_GROUP
523         uint8_t c               = *((uint8_t *)param);
524         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
525         if(c == LV_KEY_RIGHT || c == LV_KEY_UP) {
526             if(ext->showed_date.month >= 12) {
527                 ext->showed_date.month = 1;
528                 ext->showed_date.year++;
529             }
530             else {
531                 ext->showed_date.month++;
532             }
533             lv_obj_invalidate(calendar);
534         }
535         else if(c == LV_KEY_LEFT || c == LV_KEY_DOWN) {
536             if(ext->showed_date.month <= 1) {
537                 ext->showed_date.month = 12;
538                 ext->showed_date.year--;
539             }
540             else {
541                 ext->showed_date.month--;
542             }
543             lv_obj_invalidate(calendar);
544         }
545 #endif
546     }
547 
548     return res;
549 }
550 
551 /**
552  * Get the style descriptor of a part of the object
553  * @param page pointer the object
554  * @param part the part from `lv_calendar_part_t`. (LV_CALENDAR_PART_...)
555  * @return pointer to the style descriptor of the specified part
556  */
lv_calendar_get_style(lv_obj_t * calendar,uint8_t part)557 static lv_style_list_t * lv_calendar_get_style(lv_obj_t * calendar, uint8_t part)
558 {
559     LV_ASSERT_OBJ(calendar, LV_OBJX_NAME);
560 
561     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
562     lv_style_list_t * style_dsc_p;
563 
564 
565     switch(part) {
566         case LV_CALENDAR_PART_BG:
567             style_dsc_p = &calendar->style_list;
568             break;
569         case LV_CALENDAR_PART_HEADER:
570             style_dsc_p = &ext->style_header;
571             break;
572         case LV_CALENDAR_PART_DAY_NAMES:
573             style_dsc_p = &ext->style_day_names;
574             break;
575         case LV_CALENDAR_PART_DATE:
576             style_dsc_p = &ext->style_date_nums;
577             break;
578         default:
579             style_dsc_p = NULL;
580     }
581 
582     return style_dsc_p;
583 }
584 
585 /**
586  * It will check if the days part of calendar is touched
587  * and if it is, it will calculate the day and put it in pressed_date of calendar object.
588  * @param calendar pointer to a calendar object
589  * @param pointer to a point
590  * @return true: days part of calendar is touched and its related date is put in pressed date
591  * false: the point is out of days part area.
592  */
calculate_touched_day(lv_obj_t * calendar,const lv_point_t * touched_point)593 static bool calculate_touched_day(lv_obj_t * calendar, const lv_point_t * touched_point)
594 {
595     lv_area_t days_area;
596     lv_area_copy(&days_area, &calendar->coords);
597     lv_style_int_t left = lv_obj_get_style_pad_left(calendar, LV_CALENDAR_PART_DATE);
598     lv_style_int_t right = lv_obj_get_style_pad_right(calendar, LV_CALENDAR_PART_DATE);
599     lv_style_int_t top = lv_obj_get_style_pad_top(calendar, LV_CALENDAR_PART_DATE);
600     lv_style_int_t bottom = lv_obj_get_style_pad_bottom(calendar, LV_CALENDAR_PART_DATE);
601 
602     days_area.x1 += left;
603     days_area.x2 -= right;
604     days_area.y1 = calendar->coords.y1 + get_header_height(calendar) + get_day_names_height(calendar) + top;
605     days_area.y2 -= bottom;
606 
607     if(_lv_area_is_point_on(&days_area, touched_point, 0)) {
608         lv_coord_t w  = (days_area.x2 - days_area.x1 + 1) / 7;
609         lv_coord_t h  = (days_area.y2 - days_area.y1 + 1) / 6;
610         uint8_t x_pos = 0;
611         x_pos         = (touched_point->x - days_area.x1) / w;
612         if(x_pos > 6) x_pos = 6;
613         uint8_t y_pos = 0;
614         y_pos         = (touched_point->y - days_area.y1) / h;
615         if(y_pos > 5) y_pos = 5;
616 
617         uint8_t i_pos           = 0;
618         i_pos                   = (y_pos * 7) + x_pos;
619         lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
620         if(i_pos < get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1)) {
621             ext->pressed_date.year  = ext->showed_date.year - (ext->showed_date.month == 1 ? 1 : 0);
622             ext->pressed_date.month = ext->showed_date.month == 1 ? 12 : (ext->showed_date.month - 1);
623             ext->pressed_date.day   = get_month_length(ext->pressed_date.year, ext->pressed_date.month) -
624                                       get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1) + 1 + i_pos;
625         }
626         else if(i_pos < (get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1) +
627                          get_month_length(ext->showed_date.year, ext->showed_date.month))) {
628             ext->pressed_date.year  = ext->showed_date.year;
629             ext->pressed_date.month = ext->showed_date.month;
630             ext->pressed_date.day   = i_pos + 1 - get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1);
631         }
632         else if(i_pos < 42) {
633             ext->pressed_date.year  = ext->showed_date.year + (ext->showed_date.month == 12 ? 1 : 0);
634             ext->pressed_date.month = ext->showed_date.month == 12 ? 1 : (ext->showed_date.month + 1);
635             ext->pressed_date.day   = i_pos + 1 - get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1) -
636                                       get_month_length(ext->showed_date.year, ext->showed_date.month);
637         }
638         return true;
639     }
640     else {
641         return false;
642     }
643 }
644 
645 /**
646  * Get the height of a calendar's header based on it's style
647  * @param calendar point to a calendar
648  * @return the header's height
649  */
get_header_height(lv_obj_t * calendar)650 static lv_coord_t get_header_height(lv_obj_t * calendar)
651 {
652     const lv_font_t * font = lv_obj_get_style_text_font(calendar, LV_CALENDAR_PART_HEADER);
653     lv_style_int_t top = lv_obj_get_style_pad_top(calendar, LV_CALENDAR_PART_HEADER);
654     lv_style_int_t bottom = lv_obj_get_style_pad_bottom(calendar, LV_CALENDAR_PART_HEADER);
655 
656     return lv_font_get_line_height(font) + top + bottom;
657 }
658 
659 /**
660  * Get the height of a calendar's day_names based on it's style
661  * @param calendar point to a calendar
662  * @return the day_names's height
663  */
get_day_names_height(lv_obj_t * calendar)664 static lv_coord_t get_day_names_height(lv_obj_t * calendar)
665 {
666     const lv_font_t * font = lv_obj_get_style_text_font(calendar, LV_CALENDAR_PART_DAY_NAMES);
667     lv_style_int_t top = lv_obj_get_style_pad_top(calendar, LV_CALENDAR_PART_DAY_NAMES);
668     lv_style_int_t bottom = lv_obj_get_style_pad_bottom(calendar, LV_CALENDAR_PART_DAY_NAMES);
669 
670     return lv_font_get_line_height(font) + top + bottom;
671 }
672 
673 /**
674  * Draw the calendar header with month name and arrows
675  * @param calendar point to a calendar
676  * @param mask a mask for drawing
677  */
draw_header(lv_obj_t * calendar,const lv_area_t * mask)678 static void draw_header(lv_obj_t * calendar, const lv_area_t * mask)
679 {
680     lv_style_int_t header_top = lv_obj_get_style_pad_top(calendar, LV_CALENDAR_PART_HEADER);
681     lv_style_int_t header_left = lv_obj_get_style_pad_left(calendar, LV_CALENDAR_PART_HEADER);
682     lv_style_int_t header_right = lv_obj_get_style_pad_right(calendar, LV_CALENDAR_PART_HEADER);
683     const lv_font_t * font = lv_obj_get_style_text_font(calendar, LV_CALENDAR_PART_HEADER);
684 
685     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
686 
687     lv_area_t header_area;
688     header_area.x1 = calendar->coords.x1;
689     header_area.x2 = calendar->coords.x2;
690     header_area.y1 = calendar->coords.y1 + header_top;
691     header_area.y2 = header_area.y1 + lv_font_get_line_height(font);
692 
693     lv_draw_rect_dsc_t header_rect_dsc;
694     lv_draw_rect_dsc_init(&header_rect_dsc);
695     lv_obj_init_draw_rect_dsc(calendar, LV_CALENDAR_PART_HEADER, &header_rect_dsc);
696     lv_draw_rect(&header_area, mask, &header_rect_dsc);
697 
698     lv_state_t state_ori = calendar->state;
699 
700     /*Add the year + month name*/
701     char txt_buf[64];
702     _lv_utils_num_to_str(ext->showed_date.year, txt_buf);
703     txt_buf[4] = ' ';
704     txt_buf[5] = '\0';
705     strcpy(&txt_buf[5], get_month_name(calendar, ext->showed_date.month));
706 
707     calendar->state = LV_STATE_DEFAULT;
708     _lv_obj_disable_style_caching(calendar, true);
709 
710     lv_draw_label_dsc_t label_dsc;
711     lv_draw_label_dsc_init(&label_dsc);
712     lv_obj_init_draw_label_dsc(calendar, LV_CALENDAR_PART_HEADER, &label_dsc);
713     label_dsc.flag = LV_TXT_FLAG_CENTER;
714     lv_draw_label(&header_area, mask, &label_dsc, txt_buf, NULL);
715 
716     calendar->state = state_ori;    /*Restore the state*/
717 
718     /*Add the left arrow*/
719     if(ext->btn_pressing < 0) calendar->state |= LV_STATE_PRESSED;
720     else calendar->state &= ~(LV_STATE_PRESSED);
721 
722     header_area.x1 += header_left;
723 
724     lv_draw_label_dsc_init(&label_dsc);
725     lv_obj_init_draw_label_dsc(calendar, LV_CALENDAR_PART_HEADER, &label_dsc);
726     lv_draw_label(&header_area, mask, &label_dsc, LV_SYMBOL_LEFT, NULL);
727 
728     calendar->state = state_ori;    /*Restore the state*/
729 
730     /*Add the right arrow*/
731     if(ext->btn_pressing > 0) calendar->state |= LV_STATE_PRESSED;
732     else calendar->state &= ~(LV_STATE_PRESSED);
733 
734     header_area.x1 = header_area.x2 - header_right - _lv_txt_get_width(LV_SYMBOL_RIGHT, (uint16_t)strlen(LV_SYMBOL_RIGHT),
735                                                                        font, 0, LV_TXT_FLAG_NONE);
736 
737     lv_draw_label_dsc_init(&label_dsc);
738     lv_obj_init_draw_label_dsc(calendar, LV_CALENDAR_PART_HEADER, &label_dsc);
739     lv_draw_label(&header_area, mask, &label_dsc, LV_SYMBOL_RIGHT, NULL);
740 
741     calendar->state = state_ori;    /*Restore the state*/
742     _lv_obj_disable_style_caching(calendar, false);
743 }
744 
745 /**
746  * Draw the day's name below the header
747  * @param calendar point to a calendar
748  * @param mask a mask for drawing
749  */
draw_day_names(lv_obj_t * calendar,const lv_area_t * mask)750 static void draw_day_names(lv_obj_t * calendar, const lv_area_t * mask)
751 {
752     lv_style_int_t date_top = lv_obj_get_style_pad_top(calendar, LV_CALENDAR_PART_DATE);
753     lv_style_int_t date_bottom = lv_obj_get_style_pad_bottom(calendar, LV_CALENDAR_PART_DATE);
754     lv_style_int_t date_left = lv_obj_get_style_pad_left(calendar, LV_CALENDAR_PART_DATE);
755     lv_style_int_t date_right = lv_obj_get_style_pad_right(calendar, LV_CALENDAR_PART_DATE);
756     lv_style_int_t date_inner = lv_obj_get_style_pad_inner(calendar, LV_CALENDAR_PART_DATE);
757 
758     lv_coord_t days_w      = lv_obj_get_width(calendar) - date_left - date_right;
759     lv_coord_t box_w      = (days_w - date_inner * 6) / 7;
760     lv_coord_t days_y1 = calendar->coords.y1 + date_top + get_header_height(calendar) + get_day_names_height(calendar);
761     lv_coord_t days_h = calendar->coords.y2 - days_y1 - date_bottom;
762     lv_coord_t box_h      = (days_h - 5 * date_inner) / 6;
763     lv_coord_t box_size = LV_MATH_MIN(box_w, box_h);
764 
765     lv_style_int_t left = lv_obj_get_style_pad_left(calendar, LV_CALENDAR_PART_DAY_NAMES);
766     lv_style_int_t right = lv_obj_get_style_pad_right(calendar, LV_CALENDAR_PART_DAY_NAMES);
767     lv_style_int_t top = lv_obj_get_style_pad_top(calendar, LV_CALENDAR_PART_DAY_NAMES);
768     const lv_font_t * font = lv_obj_get_style_text_font(calendar, LV_CALENDAR_PART_DAY_NAMES);
769 
770     lv_coord_t w = lv_obj_get_width(calendar) - left - right;
771 
772     lv_coord_t label_w = w / 6;
773 
774     lv_area_t label_area;
775     label_area.y1 = calendar->coords.y1 + get_header_height(calendar) + top;
776     label_area.y2 = label_area.y1 + lv_font_get_line_height(font);
777 
778     lv_draw_label_dsc_t label_dsc;
779     lv_draw_label_dsc_init(&label_dsc);
780     lv_obj_init_draw_label_dsc(calendar, LV_CALENDAR_PART_DAY_NAMES, &label_dsc);
781     label_dsc.flag = LV_TXT_FLAG_CENTER;
782 
783     uint32_t i;
784     for(i = 0; i < 7; i++) {
785         label_area.x1 = calendar->coords.x1 + ((w - box_size) * i) / 6  + box_size / 2 - label_w / 2 + left;
786         label_area.x2 = label_area.x1 + label_w - 1;
787 
788         lv_draw_label(&label_area, mask, &label_dsc, get_day_name(calendar, i), NULL);
789     }
790 }
791 
792 /**
793  * Draw the date numbers in a matrix
794  * @param calendar point to a calendar
795  * @param mask a mask for drawing
796  */
draw_dates(lv_obj_t * calendar,const lv_area_t * clip_area)797 static void draw_dates(lv_obj_t * calendar, const lv_area_t * clip_area)
798 {
799     lv_calendar_ext_t * ext     = lv_obj_get_ext_attr(calendar);
800 
801     const lv_font_t * nums_font = lv_obj_get_style_text_font(calendar, LV_CALENDAR_PART_DATE);
802 
803     lv_style_int_t date_top = lv_obj_get_style_pad_top(calendar, LV_CALENDAR_PART_DATE);
804     lv_style_int_t date_bottom = lv_obj_get_style_pad_bottom(calendar, LV_CALENDAR_PART_DATE);
805     lv_style_int_t date_left = lv_obj_get_style_pad_left(calendar, LV_CALENDAR_PART_DATE);
806     lv_style_int_t date_right = lv_obj_get_style_pad_right(calendar, LV_CALENDAR_PART_DATE);
807     lv_style_int_t date_inner = lv_obj_get_style_pad_inner(calendar, LV_CALENDAR_PART_DATE);
808 
809     lv_coord_t days_y1 = calendar->coords.y1 + date_top + get_header_height(calendar) + get_day_names_height(calendar);
810     lv_coord_t days_h = calendar->coords.y2 - days_y1 - date_bottom;
811 
812     /*The state changes without re-caching the styles, disable the use of cache*/
813     lv_state_t state_ori = calendar->state;
814     calendar->state = LV_STATE_DEFAULT;
815     _lv_obj_disable_style_caching(calendar, true);
816 
817     lv_state_t month_state = LV_STATE_DISABLED;
818 
819     uint8_t day_cnt;
820     lv_coord_t days_w      = lv_obj_get_width(calendar) - date_left - date_right;
821     lv_coord_t box_w      = (days_w - date_inner * 6) / 7;
822     lv_coord_t box_h      = (days_h - 5 * date_inner) / 6;
823     lv_coord_t box_size = LV_MATH_MIN(box_w, box_h);
824 
825     uint8_t month_start_day = get_day_of_week(ext->showed_date.year, ext->showed_date.month, 1);
826 
827     day_draw_state_t draw_state;
828 
829     /*If starting with the first day of the week then the previous month is not visible*/
830     if(month_start_day == 0) {
831         day_cnt    = 1;
832         draw_state = DAY_DRAW_ACT_MONTH;
833         month_state  = 0;
834     }
835     else {
836         draw_state = DAY_DRAW_PREV_MONTH;
837         day_cnt = get_month_length(ext->showed_date.year, ext->showed_date.month - 1); /*Length of the previous month*/
838         day_cnt -= month_start_day - 1; /*First visible number of the previous month*/
839         month_state = LV_STATE_DISABLED;
840     }
841 
842     bool month_of_today_shown = false;
843     if(ext->showed_date.year == ext->today.year && ext->showed_date.month == ext->today.month) {
844         month_of_today_shown = true;
845     }
846 
847     char buf[3];
848 
849     /*Draw 6 weeks*/
850     lv_draw_rect_dsc_t rect_dsc;
851     lv_draw_label_dsc_t label_dsc;
852     lv_state_t prev_state = 0xFF;
853     uint32_t week;
854     for(week = 0; week < 6; week++) {
855         lv_area_t box_area;
856 
857         box_area.y1 = days_y1 + ((days_h - box_size) * week) / 5;
858         box_area.y2 = box_area.y1 + box_size - 1;
859 
860         if(box_area.y1 > clip_area->y2) {
861             calendar->state = state_ori;
862             _lv_obj_disable_style_caching(calendar, false);
863             return;
864         }
865 
866         lv_area_t label_area;
867         label_area.y1 = box_area.y1 + (lv_area_get_height(&box_area) - lv_font_get_line_height(nums_font)) / 2;
868         label_area.y2 = label_area.y1 + lv_font_get_line_height(nums_font);
869 
870         /*Draw the 7 days of a week*/
871         uint32_t day;
872         for(day = 0; day < 7; day++) {
873             /*The previous month is over*/
874             if(draw_state == DAY_DRAW_PREV_MONTH && day == month_start_day) {
875                 draw_state = DAY_DRAW_ACT_MONTH;
876                 day_cnt    = 1;
877                 month_state  = 0;
878             }
879             /*The current month is over*/
880             else if(draw_state == DAY_DRAW_ACT_MONTH &&
881                     day_cnt > get_month_length(ext->showed_date.year, ext->showed_date.month)) {
882                 draw_state = DAY_DRAW_NEXT_MONTH;
883                 day_cnt    = 1;
884                 month_state  = LV_STATE_DISABLED;
885             }
886 
887             if(box_area.y2 < clip_area->y1) {
888                 day_cnt++;
889                 continue;
890             }
891 
892             lv_state_t day_state = month_state;
893             if(is_pressed(calendar, draw_state, ext->showed_date.year, ext->showed_date.month, day_cnt)) {
894                 day_state |= LV_STATE_PRESSED;
895             }
896             if(is_highlighted(calendar, draw_state, ext->showed_date.year, ext->showed_date.month, day_cnt)) {
897                 day_state |= LV_STATE_CHECKED;
898             }
899             if(month_of_today_shown && day_cnt == ext->today.day && draw_state == DAY_DRAW_ACT_MONTH) {
900                 day_state |= LV_STATE_FOCUSED;
901             }
902 
903             if(prev_state != day_state) {
904                 lv_draw_rect_dsc_init(&rect_dsc);
905                 lv_draw_label_dsc_init(&label_dsc);
906                 label_dsc.flag = LV_TXT_FLAG_CENTER;
907 
908                 calendar->state = day_state;
909                 lv_obj_init_draw_label_dsc(calendar, LV_CALENDAR_PART_DATE, &label_dsc);
910                 lv_obj_init_draw_rect_dsc(calendar, LV_CALENDAR_PART_DATE, &rect_dsc);
911 
912                 prev_state = day_state;
913             }
914 
915             label_area.x1 = calendar->coords.x1 + ((days_w - box_size) * day) / 6 + date_left;
916             label_area.x2 = label_area.x1 + box_size - 1;
917 
918             box_area.x1 = label_area.x1;
919             box_area.x2 = label_area.x2;
920 
921 
922             lv_draw_rect(&box_area, clip_area, &rect_dsc);
923 
924             /*Write the day's number*/
925             _lv_utils_num_to_str(day_cnt, buf);
926             lv_draw_label(&label_area, clip_area, &label_dsc, buf, NULL);
927 
928             /*Go to the next day*/
929             day_cnt++;
930         }
931     }
932     calendar->state = state_ori;
933     _lv_obj_disable_style_caching(calendar, false);
934 
935 
936 }
937 
938 /**
939  * Check weather a date is highlighted or not
940  * @param calendar pointer to a calendar object
941  * @param draw_state which month is drawn (previous, active, next)
942  * @param year a year
943  * @param month a  month [1..12]
944  * @param day a day [1..31]
945  * @return true: highlighted
946  */
is_highlighted(lv_obj_t * calendar,day_draw_state_t draw_state,int32_t year,int32_t month,int32_t day)947 static bool is_highlighted(lv_obj_t * calendar, day_draw_state_t draw_state, int32_t year, int32_t month, int32_t day)
948 {
949     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
950 
951 
952     if(draw_state == DAY_DRAW_PREV_MONTH) {
953         year -= month == 1 ? 1 : 0;
954         month = month == 1 ? 12 : month - 1;
955     }
956     else if(draw_state == DAY_DRAW_NEXT_MONTH) {
957         year += month == 12 ? 1 : 0;
958         month = month == 12 ? 1 : month + 1;
959     }
960 
961     uint32_t i;
962     for(i = 0; i < ext->highlighted_dates_num; i++) {
963         if(ext->highlighted_dates[i].year == year && ext->highlighted_dates[i].month == month &&
964            ext->highlighted_dates[i].day == day) {
965             return true;
966         }
967     }
968 
969     return false;
970 }
971 
972 /**
973  * Check weather a date is highlighted or not
974  * @param calendar pointer to a calendar object
975  * @param draw_state which month is drawn (previous, active, next)
976  * @param year a year
977  * @param month a  month [1..12]
978  * @param day a day [1..31]
979  * @return true: highlighted
980  */
is_pressed(lv_obj_t * calendar,day_draw_state_t draw_state,int32_t year,int32_t month,int32_t day)981 static bool is_pressed(lv_obj_t * calendar, day_draw_state_t draw_state, int32_t year, int32_t month, int32_t day)
982 {
983     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
984 
985 
986     if(draw_state == DAY_DRAW_PREV_MONTH) {
987         year -= month == 1 ? 1 : 0;
988         month = month == 1 ? 12 : month - 1;
989     }
990     else if(draw_state == DAY_DRAW_NEXT_MONTH) {
991         year += month == 12 ? 1 : 0;
992         month = month == 12 ? 1 : month + 1;
993     }
994 
995     if(year == ext->pressed_date.year && month == ext->pressed_date.month && day == ext->pressed_date.day) return true;
996     else return false;
997 }
998 /**
999  * Get the day name
1000  * @param calendar pointer to a calendar object
1001  * @param day a day in [0..6]
1002  * @return
1003  */
get_day_name(lv_obj_t * calendar,uint8_t day)1004 static const char * get_day_name(lv_obj_t * calendar, uint8_t day)
1005 {
1006 
1007     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
1008     if(ext->day_names)
1009         return ext->day_names[day];
1010     else
1011         return day_name[day];
1012 }
1013 
1014 /**
1015  * Get the month name
1016  * @param calendar pointer to a calendar object
1017  * @param month a month. The range is basically [1..12] but [-11..1] is also supported to handle
1018  * previous year
1019  * @return
1020  */
get_month_name(lv_obj_t * calendar,int32_t month)1021 static const char * get_month_name(lv_obj_t * calendar, int32_t month)
1022 {
1023     month--; /*Range of months id [1..12] but range of indexes is [0..11]*/
1024     if(month < 0) month = 12 + month;
1025 
1026     lv_calendar_ext_t * ext = lv_obj_get_ext_attr(calendar);
1027     if(ext->month_names)
1028         return ext->month_names[month];
1029     else
1030         return month_name[month];
1031 }
1032 
1033 /**
1034  * Get the number of days in a month
1035  * @param year a year
1036  * @param month a month. The range is basically [1..12] but [-11..1] is also supported to handle
1037  * previous year
1038  * @return [28..31]
1039  */
get_month_length(int32_t year,int32_t month)1040 static uint8_t get_month_length(int32_t year, int32_t month)
1041 {
1042     month--; /*Range of months id [1..12] but range of indexes is [0..11]*/
1043     if(month < 0) {
1044         year--;             /*Already in the previous year (won't be less then -12 to skip a whole year)*/
1045         month = 12 + month; /*`month` is negative, the result will be < 12*/
1046     }
1047     if(month >= 12) {
1048         year++;
1049         month -= 12;
1050     }
1051 
1052     /*month == 1 is february*/
1053     return (month == 1) ? (28 + is_leap_year(year)) : 31 - month % 7 % 2;
1054 }
1055 
1056 /**
1057  * Tells whether a year is leap year or not
1058  * @param year a year
1059  * @return 0: not leap year; 1: leap year
1060  */
is_leap_year(uint32_t year)1061 static uint8_t is_leap_year(uint32_t year)
1062 {
1063     return (year % 4) || ((year % 100 == 0) && (year % 400)) ? 0 : 1;
1064 }
1065 
1066 /**
1067  * Get the day of the week
1068  * @param year a year
1069  * @param month a  month
1070  * @param day a day
1071  * @return [0..6] which means [Sun..Sat] or [Mon..Sun] depending on LV_CALENDAR_WEEK_STARTS_MONDAY
1072  */
get_day_of_week(uint32_t year,uint32_t month,uint32_t day)1073 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day)
1074 {
1075     uint32_t a = month < 3 ? 1 : 0;
1076     uint32_t b = year - a;
1077 
1078 #if LV_CALENDAR_WEEK_STARTS_MONDAY
1079     uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400) - 1) % 7;
1080 #else
1081     uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400)) % 7;
1082 #endif
1083 
1084     return day_of_week;
1085 }
1086 
1087 #endif
1088