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