1 /**
2 * @file lv_calendar.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_calendar_private.h"
10 #include "../../draw/lv_draw_private.h"
11 #include "../../core/lv_obj_class_private.h"
12 #include "../../../lvgl.h"
13 #if LV_USE_CALENDAR
14
15 #include "../../misc/lv_assert.h"
16
17 /*********************
18 * DEFINES
19 *********************/
20 #define LV_CALENDAR_CTRL_TODAY LV_BUTTONMATRIX_CTRL_CUSTOM_1
21 #define LV_CALENDAR_CTRL_HIGHLIGHT LV_BUTTONMATRIX_CTRL_CUSTOM_2
22
23 #define MY_CLASS (&lv_calendar_class)
24
25 /**********************
26 * TYPEDEFS
27 **********************/
28
29 /**********************
30 * STATIC PROTOTYPES
31 **********************/
32 static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
33 static void draw_task_added_event_cb(lv_event_t * e);
34
35 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day);
36 static uint8_t get_month_length(int32_t year, int32_t month);
37 static uint8_t is_leap_year(uint32_t year);
38 static void highlight_update(lv_obj_t * calendar);
39
40 #if LV_USE_CALENDAR_CHINESE
41 static lv_calendar_date_t gregorian_get_last_month_time(lv_calendar_date_t * time);
42 static lv_calendar_date_t gregorian_get_next_month_time(lv_calendar_date_t * time);
43 static void chinese_calendar_set_day_name(lv_obj_t * calendar, uint8_t index, uint8_t day,
44 lv_calendar_date_t * gregorian_time);
45 #endif
46
47 /**********************
48 * STATIC VARIABLES
49 **********************/
50
51 const lv_obj_class_t lv_calendar_class = {
52 .constructor_cb = lv_calendar_constructor,
53 .width_def = (LV_DPI_DEF * 3) / 2,
54 .height_def = (LV_DPI_DEF * 3) / 2,
55 .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
56 .instance_size = sizeof(lv_calendar_t),
57 .base_class = &lv_obj_class,
58 .name = "calendar",
59 };
60
61 static const char * day_names_def[7] = LV_CALENDAR_DEFAULT_DAY_NAMES;
62
63 /**********************
64 * MACROS
65 **********************/
66
67 /**********************
68 * GLOBAL FUNCTIONS
69 **********************/
70
lv_calendar_create(lv_obj_t * parent)71 lv_obj_t * lv_calendar_create(lv_obj_t * parent)
72 {
73 LV_LOG_INFO("begin");
74 lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_class, parent);
75 lv_obj_class_init_obj(obj);
76 return obj;
77 }
78
79 /*=====================
80 * Setter functions
81 *====================*/
82
lv_calendar_set_day_names(lv_obj_t * obj,const char * day_names[])83 void lv_calendar_set_day_names(lv_obj_t * obj, const char * day_names[])
84 {
85 LV_ASSERT_OBJ(obj, MY_CLASS);
86 lv_calendar_t * calendar = (lv_calendar_t *)obj;
87
88 uint32_t i;
89 for(i = 0; i < 7; i++) {
90 calendar->map[i] = day_names[i];
91 }
92 lv_obj_invalidate(obj);
93 }
94
lv_calendar_set_today_date(lv_obj_t * obj,uint32_t year,uint32_t month,uint32_t day)95 void lv_calendar_set_today_date(lv_obj_t * obj, uint32_t year, uint32_t month, uint32_t day)
96 {
97 LV_ASSERT_OBJ(obj, MY_CLASS);
98 lv_calendar_t * calendar = (lv_calendar_t *)obj;
99
100 calendar->today.year = year;
101 calendar->today.month = month;
102 calendar->today.day = day;
103
104 highlight_update(obj);
105 }
106
lv_calendar_set_highlighted_dates(lv_obj_t * obj,lv_calendar_date_t highlighted[],size_t date_num)107 void lv_calendar_set_highlighted_dates(lv_obj_t * obj, lv_calendar_date_t highlighted[], size_t date_num)
108 {
109 LV_ASSERT_NULL(highlighted);
110
111 LV_ASSERT_OBJ(obj, MY_CLASS);
112 lv_calendar_t * calendar = (lv_calendar_t *)obj;
113
114 calendar->highlighted_dates = highlighted;
115 calendar->highlighted_dates_num = date_num;
116
117 highlight_update(obj);
118 }
119
lv_calendar_set_month_shown(lv_obj_t * obj,uint32_t year,uint32_t month)120 void lv_calendar_set_month_shown(lv_obj_t * obj, uint32_t year, uint32_t month)
121 {
122 LV_ASSERT_OBJ(obj, MY_CLASS);
123 lv_calendar_t * calendar = (lv_calendar_t *)obj;
124
125 calendar->showed_date.year = year;
126 calendar->showed_date.month = month;
127 calendar->showed_date.day = 1;
128
129 lv_calendar_date_t d;
130 d.year = calendar->showed_date.year;
131 d.month = calendar->showed_date.month;
132 d.day = calendar->showed_date.day;
133
134 uint32_t i;
135
136 /*Remove the disabled state but revert it for day names*/
137 lv_buttonmatrix_clear_button_ctrl_all(calendar->btnm, LV_BUTTONMATRIX_CTRL_DISABLED);
138 for(i = 0; i < 7; i++) {
139 lv_buttonmatrix_set_button_ctrl(calendar->btnm, i, LV_BUTTONMATRIX_CTRL_DISABLED);
140 }
141
142 uint8_t act_mo_len = get_month_length(d.year, d.month);
143 uint8_t day_first = get_day_of_week(d.year, d.month, 1);
144 uint8_t c;
145
146 #if LV_USE_CALENDAR_CHINESE
147 lv_calendar_date_t gregorian_time;
148 gregorian_time = d;
149 #endif
150
151 for(i = day_first, c = 1; i < act_mo_len + day_first; i++, c++) {
152 #if LV_USE_CALENDAR_CHINESE
153 if(calendar->use_chinese_calendar) {
154 gregorian_time.day = c;
155 chinese_calendar_set_day_name(obj, i, c, &gregorian_time);
156 }
157 else
158 #endif
159 lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
160 }
161
162 uint8_t prev_mo_len = get_month_length(d.year, d.month - 1);
163
164 #if LV_USE_CALENDAR_CHINESE
165 gregorian_time = gregorian_get_last_month_time(&d);
166 #endif
167
168 for(i = 0, c = prev_mo_len - day_first + 1; i < day_first; i++, c++) {
169 #if LV_USE_CALENDAR_CHINESE
170 if(calendar->use_chinese_calendar) {
171 gregorian_time.day = c;
172 chinese_calendar_set_day_name(obj, i, c, &gregorian_time);
173 }
174 else
175 #endif
176 lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
177 lv_buttonmatrix_set_button_ctrl(calendar->btnm, i + 7, LV_BUTTONMATRIX_CTRL_DISABLED);
178 }
179
180 #if LV_USE_CALENDAR_CHINESE
181 gregorian_time = gregorian_get_next_month_time(&d);
182 #endif
183
184 for(i = day_first + act_mo_len, c = 1; i < 6 * 7; i++, c++) {
185 #if LV_USE_CALENDAR_CHINESE
186 if(calendar->use_chinese_calendar) {
187 gregorian_time.day = c;
188 chinese_calendar_set_day_name(obj, i, c, &gregorian_time);
189 }
190 else
191 #endif
192 lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
193 lv_buttonmatrix_set_button_ctrl(calendar->btnm, i + 7, LV_BUTTONMATRIX_CTRL_DISABLED);
194 }
195
196 highlight_update(obj);
197
198 /*Reset the focused button if the days changes*/
199 if(lv_buttonmatrix_get_selected_button(calendar->btnm) != LV_BUTTONMATRIX_BUTTON_NONE) {
200 lv_buttonmatrix_set_selected_button(calendar->btnm, day_first + 7);
201 }
202
203 lv_obj_invalidate(obj);
204
205 /* The children of the calendar are probably headers.
206 * Notify them to let the headers updated to the new date*/
207 uint32_t child_cnt = lv_obj_get_child_count(obj);
208 for(i = 0; i < child_cnt; i++) {
209 lv_obj_t * child = lv_obj_get_child(obj, i);
210 if(child == calendar->btnm) continue;
211 lv_obj_send_event(child, LV_EVENT_VALUE_CHANGED, obj);
212 }
213 }
214
215 /*=====================
216 * Getter functions
217 *====================*/
218
lv_calendar_get_btnmatrix(const lv_obj_t * obj)219 lv_obj_t * lv_calendar_get_btnmatrix(const lv_obj_t * obj)
220 {
221 LV_ASSERT_OBJ(obj, MY_CLASS);
222 const lv_calendar_t * calendar = (lv_calendar_t *)obj;
223 return calendar->btnm;
224 }
225
lv_calendar_get_today_date(const lv_obj_t * obj)226 const lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * obj)
227 {
228 LV_ASSERT_OBJ(obj, MY_CLASS);
229 const lv_calendar_t * calendar = (lv_calendar_t *)obj;
230
231 return &calendar->today;
232 }
233
lv_calendar_get_showed_date(const lv_obj_t * obj)234 const lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * obj)
235 {
236 LV_ASSERT_OBJ(obj, MY_CLASS);
237 const lv_calendar_t * calendar = (lv_calendar_t *)obj;
238
239 return &calendar->showed_date;
240 }
241
lv_calendar_get_highlighted_dates(const lv_obj_t * obj)242 lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * obj)
243 {
244 LV_ASSERT_OBJ(obj, MY_CLASS);
245 lv_calendar_t * calendar = (lv_calendar_t *)obj;
246
247 return calendar->highlighted_dates;
248 }
249
lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj)250 size_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj)
251 {
252 LV_ASSERT_OBJ(obj, MY_CLASS);
253 lv_calendar_t * calendar = (lv_calendar_t *)obj;
254
255 return calendar->highlighted_dates_num;
256 }
257
lv_calendar_get_pressed_date(const lv_obj_t * obj,lv_calendar_date_t * date)258 lv_result_t lv_calendar_get_pressed_date(const lv_obj_t * obj, lv_calendar_date_t * date)
259 {
260 LV_ASSERT_OBJ(obj, MY_CLASS);
261 lv_calendar_t * calendar = (lv_calendar_t *)obj;
262
263 uint32_t d = lv_buttonmatrix_get_selected_button(calendar->btnm);
264 if(d == LV_BUTTONMATRIX_BUTTON_NONE) {
265 date->year = 0;
266 date->month = 0;
267 date->day = 0;
268 return LV_RESULT_INVALID;
269 }
270
271 const char * txt = lv_buttonmatrix_get_button_text(calendar->btnm, lv_buttonmatrix_get_selected_button(calendar->btnm));
272
273 if(txt[1] == 0) date->day = txt[0] - '0';
274 else date->day = (txt[0] - '0') * 10 + (txt[1] - '0');
275
276 date->year = calendar->showed_date.year;
277 date->month = calendar->showed_date.month;
278
279 return LV_RESULT_OK;
280 }
281
282 /**********************
283 * STATIC FUNCTIONS
284 **********************/
285
lv_calendar_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)286 static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
287 {
288 LV_UNUSED(class_p);
289 lv_calendar_t * calendar = (lv_calendar_t *)obj;
290
291 /*Initialize the allocated 'ext'*/
292
293 calendar->today.year = 2024;
294 calendar->today.month = 1;
295 calendar->today.day = 1;
296
297 calendar->showed_date.year = 2024;
298 calendar->showed_date.month = 1;
299 calendar->showed_date.day = 1;
300
301 calendar->highlighted_dates = NULL;
302 calendar->highlighted_dates_num = 0;
303
304 lv_memzero(calendar->nums, sizeof(calendar->nums));
305 uint8_t i;
306 uint8_t j = 0;
307 for(i = 0; i < 8 * 7; i++) {
308 /*Every 8th string is "\n"*/
309 if(i != 0 && (i + 1) % 8 == 0) {
310 calendar->map[i] = "\n";
311 }
312 else if(i < 7) {
313 calendar->map[i] = day_names_def[i];
314 }
315 else {
316 calendar->nums[j][0] = 'x';
317 calendar->map[i] = calendar->nums[j];
318 j++;
319 }
320 }
321 calendar->map[8 * 7 - 1] = "";
322
323 calendar->btnm = lv_buttonmatrix_create(obj);
324 lv_buttonmatrix_set_map(calendar->btnm, calendar->map);
325 lv_buttonmatrix_set_button_ctrl_all(calendar->btnm, LV_BUTTONMATRIX_CTRL_CLICK_TRIG | LV_BUTTONMATRIX_CTRL_NO_REPEAT);
326 lv_obj_add_event_cb(calendar->btnm, draw_task_added_event_cb, LV_EVENT_DRAW_TASK_ADDED, NULL);
327 lv_obj_set_width(calendar->btnm, lv_pct(100));
328 lv_obj_add_flag(calendar->btnm, LV_OBJ_FLAG_EVENT_BUBBLE | LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS);
329
330 lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
331 lv_obj_set_flex_grow(calendar->btnm, 1);
332
333 lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
334
335 lv_calendar_set_month_shown(obj, calendar->showed_date.year, calendar->showed_date.month);
336 lv_calendar_set_today_date(obj, calendar->today.year, calendar->today.month, calendar->today.day);
337 }
338
draw_task_added_event_cb(lv_event_t * e)339 static void draw_task_added_event_cb(lv_event_t * e)
340 {
341 lv_obj_t * obj = lv_event_get_current_target(e);
342 lv_draw_task_t * draw_task = lv_event_get_param(e);
343 if(((lv_draw_dsc_base_t *)draw_task->draw_dsc)->part != LV_PART_ITEMS) return;
344
345 lv_draw_fill_dsc_t * fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task);
346 lv_draw_border_dsc_t * border_draw_dsc = lv_draw_task_get_border_dsc(draw_task);
347
348 if(!fill_draw_dsc && !border_draw_dsc) {
349 return;
350 }
351
352 int32_t id = ((lv_draw_dsc_base_t *)draw_task->draw_dsc)->id1;
353
354 /*Day name styles*/
355 if(id < 7) {
356 if(fill_draw_dsc) fill_draw_dsc->opa = LV_OPA_TRANSP;
357 if(border_draw_dsc) border_draw_dsc->opa = LV_OPA_TRANSP;
358 }
359 else if(lv_buttonmatrix_has_button_ctrl(obj, id, LV_BUTTONMATRIX_CTRL_DISABLED)) {
360 if(fill_draw_dsc) fill_draw_dsc->opa = LV_OPA_TRANSP;
361 if(border_draw_dsc) border_draw_dsc->opa = LV_OPA_TRANSP;
362 }
363
364 if(lv_buttonmatrix_has_button_ctrl(obj, id, LV_CALENDAR_CTRL_HIGHLIGHT)) {
365 if(border_draw_dsc) border_draw_dsc->color = lv_theme_get_color_primary(obj);
366 if(fill_draw_dsc) fill_draw_dsc->opa = LV_OPA_40;
367 if(fill_draw_dsc) fill_draw_dsc->color = lv_theme_get_color_primary(obj);
368 if(lv_buttonmatrix_get_selected_button(obj) == (uint32_t)id) {
369 if(fill_draw_dsc) fill_draw_dsc->opa = LV_OPA_70;
370 }
371 }
372
373 if(lv_buttonmatrix_has_button_ctrl(obj, id, LV_CALENDAR_CTRL_TODAY)) {
374 if(border_draw_dsc) border_draw_dsc->opa = LV_OPA_COVER;
375 if(border_draw_dsc) border_draw_dsc->color = lv_theme_get_color_primary(obj);
376 if(border_draw_dsc) border_draw_dsc->width += 1;
377 }
378 }
379
380 /**
381 * Get the number of days in a month
382 * @param year a year
383 * @param month a month. The range is basically [1..12] but [-11..0] or [13..24] is also
384 * supported to handle next/prev. year
385 * @return [28..31]
386 */
get_month_length(int32_t year,int32_t month)387 static uint8_t get_month_length(int32_t year, int32_t month)
388 {
389 month--;
390 if(month < 0) {
391 year--; /*Already in the previous year (won't be less than -12 to skip a whole year)*/
392 month = 12 + month; /*`month` is negative, the result will be < 12*/
393 }
394 if(month >= 12) {
395 year++;
396 month -= 12;
397 }
398
399 /*month == 1 is february*/
400 return (month == 1) ? (28 + is_leap_year(year)) : 31 - month % 7 % 2;
401 }
402
403 /**
404 * Tells whether a year is leap year or not
405 * @param year a year
406 * @return 0: not leap year; 1: leap year
407 */
is_leap_year(uint32_t year)408 static uint8_t is_leap_year(uint32_t year)
409 {
410 return (year % 4) || ((year % 100 == 0) && (year % 400)) ? 0 : 1;
411 }
412
413 /**
414 * Get the day of the week
415 * @param year a year
416 * @param month a month [1..12]
417 * @param day a day [1..32]
418 * @return [0..6] which means [Sun..Sat] or [Mon..Sun] depending on LV_CALENDAR_WEEK_STARTS_MONDAY
419 */
get_day_of_week(uint32_t year,uint32_t month,uint32_t day)420 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day)
421 {
422 uint32_t a = month < 3 ? 1 : 0;
423 uint32_t b = year - a;
424
425 #if LV_CALENDAR_WEEK_STARTS_MONDAY
426 uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400) - 1) % 7;
427 #else
428 uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400)) % 7;
429 #endif
430
431 return day_of_week ;
432 }
433
highlight_update(lv_obj_t * obj)434 static void highlight_update(lv_obj_t * obj)
435 {
436 lv_calendar_t * calendar = (lv_calendar_t *)obj;
437 uint32_t i;
438
439 /*Clear all kind of selection*/
440 lv_buttonmatrix_clear_button_ctrl_all(calendar->btnm, LV_CALENDAR_CTRL_TODAY | LV_CALENDAR_CTRL_HIGHLIGHT);
441
442 uint8_t day_first = get_day_of_week(calendar->showed_date.year, calendar->showed_date.month, 1);
443 if(calendar->highlighted_dates) {
444 for(i = 0; i < calendar->highlighted_dates_num; i++) {
445 if(calendar->highlighted_dates[i].year == calendar->showed_date.year &&
446 calendar->highlighted_dates[i].month == calendar->showed_date.month) {
447 lv_buttonmatrix_set_button_ctrl(calendar->btnm, calendar->highlighted_dates[i].day - 1 + day_first + 7,
448 LV_CALENDAR_CTRL_HIGHLIGHT);
449 }
450 }
451 }
452
453 if(calendar->showed_date.year == calendar->today.year && calendar->showed_date.month == calendar->today.month) {
454 lv_buttonmatrix_set_button_ctrl(calendar->btnm, calendar->today.day - 1 + day_first + 7, LV_CALENDAR_CTRL_TODAY);
455 }
456 }
457
458 #if LV_USE_CALENDAR_CHINESE
459
gregorian_get_last_month_time(lv_calendar_date_t * time)460 static lv_calendar_date_t gregorian_get_last_month_time(lv_calendar_date_t * time)
461 {
462 lv_calendar_date_t last_month_time;
463 if(time->month == 1) {
464 last_month_time.month = 12;
465 last_month_time.year = time->year - 1;
466 }
467 else {
468 last_month_time.month = time->month - 1;
469 last_month_time.year = time->year;
470 }
471 return last_month_time;
472 }
473
gregorian_get_next_month_time(lv_calendar_date_t * time)474 static lv_calendar_date_t gregorian_get_next_month_time(lv_calendar_date_t * time)
475 {
476 lv_calendar_date_t next_month_time;
477 if(time->month == 12) {
478 next_month_time.month = 1;
479 next_month_time.year = time->year + 1;
480 }
481 else {
482 next_month_time.month = time->month + 1;
483 next_month_time.year = time->year;
484 }
485 return next_month_time;
486 }
487
chinese_calendar_set_day_name(lv_obj_t * obj,uint8_t index,uint8_t day,lv_calendar_date_t * gregorian_time)488 static void chinese_calendar_set_day_name(lv_obj_t * obj, uint8_t index, uint8_t day,
489 lv_calendar_date_t * gregorian_time)
490 {
491 lv_calendar_t * calendar = (lv_calendar_t *)obj;
492 const char * day_name = lv_calendar_get_day_name(gregorian_time);
493 if(day_name != NULL)
494 lv_snprintf(calendar->nums[index], sizeof(calendar->nums[0]),
495 "%d\n%s",
496 day,
497 day_name);
498 else
499 lv_snprintf(calendar->nums[index], sizeof(calendar->nums[0]), "%d", day);
500 }
501 #endif
502
503 #endif /*LV_USE_CALENDAR*/
504