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