1 /**
2 * @file lv_calendar.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_calendar.h"
10 #include "../../../lvgl.h"
11 #if LV_USE_CALENDAR
12
13 #include "../../../misc/lv_assert.h"
14
15 /*********************
16 * DEFINES
17 *********************/
18 #define LV_CALENDAR_CTRL_TODAY LV_BTNMATRIX_CTRL_CUSTOM_1
19 #define LV_CALENDAR_CTRL_HIGHLIGHT LV_BTNMATRIX_CTRL_CUSTOM_2
20
21 #define MY_CLASS &lv_calendar_class
22
23 /**********************
24 * TYPEDEFS
25 **********************/
26
27 /**********************
28 * STATIC PROTOTYPES
29 **********************/
30 static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
31 static void draw_part_begin_event_cb(lv_event_t * e);
32
33 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day);
34 static uint8_t get_month_length(int32_t year, int32_t month);
35 static uint8_t is_leap_year(uint32_t year);
36 static void highlight_update(lv_obj_t * calendar);
37
38 /**********************
39 * STATIC VARIABLES
40 **********************/
41 const lv_obj_class_t lv_calendar_class = {
42 .constructor_cb = lv_calendar_constructor,
43 .width_def = (LV_DPI_DEF * 3) / 2,
44 .height_def = (LV_DPI_DEF * 3) / 2,
45 .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
46 .instance_size = sizeof(lv_calendar_t),
47 .base_class = &lv_obj_class
48 };
49
50 static const char * day_names_def[7] = LV_CALENDAR_DEFAULT_DAY_NAMES;
51
52 /**********************
53 * MACROS
54 **********************/
55
56 /**********************
57 * GLOBAL FUNCTIONS
58 **********************/
59
lv_calendar_create(lv_obj_t * parent)60 lv_obj_t * lv_calendar_create(lv_obj_t * parent)
61 {
62 LV_LOG_INFO("begin");
63 lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_class, parent);
64 lv_obj_class_init_obj(obj);
65 return obj;
66 }
67
68 /*=====================
69 * Setter functions
70 *====================*/
71
lv_calendar_set_day_names(lv_obj_t * obj,const char * day_names[])72 void lv_calendar_set_day_names(lv_obj_t * obj, const char * day_names[])
73 {
74 LV_ASSERT_OBJ(obj, MY_CLASS);
75 lv_calendar_t * calendar = (lv_calendar_t *)obj;
76
77 uint32_t i;
78 for(i = 0; i < 7; i++) {
79 calendar->map[i] = day_names[i];
80 }
81 lv_obj_invalidate(obj);
82 }
83
lv_calendar_set_today_date(lv_obj_t * obj,uint32_t year,uint32_t month,uint32_t day)84 void lv_calendar_set_today_date(lv_obj_t * obj, uint32_t year, uint32_t month, uint32_t day)
85 {
86 LV_ASSERT_OBJ(obj, MY_CLASS);
87 lv_calendar_t * calendar = (lv_calendar_t *)obj;
88
89 calendar->today.year = year;
90 calendar->today.month = month;
91 calendar->today.day = day;
92
93 highlight_update(obj);
94 }
95
lv_calendar_set_highlighted_dates(lv_obj_t * obj,lv_calendar_date_t highlighted[],uint16_t date_num)96 void lv_calendar_set_highlighted_dates(lv_obj_t * obj, lv_calendar_date_t highlighted[], uint16_t date_num)
97 {
98 LV_ASSERT_NULL(highlighted);
99
100 LV_ASSERT_OBJ(obj, MY_CLASS);
101 lv_calendar_t * calendar = (lv_calendar_t *)obj;
102
103 calendar->highlighted_dates = highlighted;
104 calendar->highlighted_dates_num = date_num;
105
106 highlight_update(obj);
107 }
108
lv_calendar_set_showed_date(lv_obj_t * obj,uint32_t year,uint32_t month)109 void lv_calendar_set_showed_date(lv_obj_t * obj, uint32_t year, uint32_t month)
110 {
111 LV_ASSERT_OBJ(obj, MY_CLASS);
112 lv_calendar_t * calendar = (lv_calendar_t *)obj;
113
114 calendar->showed_date.year = year;
115 calendar->showed_date.month = month;
116 calendar->showed_date.day = 1;
117
118 lv_calendar_date_t d;
119 d.year = calendar->showed_date.year;
120 d.month = calendar->showed_date.month;
121 d.day = calendar->showed_date.day;
122
123 uint8_t i;
124
125 /*Remove the disabled state but revert it for day names*/
126 lv_btnmatrix_clear_btn_ctrl_all(calendar->btnm, LV_BTNMATRIX_CTRL_DISABLED);
127 for(i = 0; i < 7; i++) {
128 lv_btnmatrix_set_btn_ctrl(calendar->btnm, i, LV_BTNMATRIX_CTRL_DISABLED);
129 }
130
131 uint8_t act_mo_len = get_month_length(d.year, d.month);
132 uint8_t day_first = get_day_of_week(d.year, d.month, 1);
133 uint8_t c;
134 for(i = day_first, c = 1; i < act_mo_len + day_first; i++, c++) {
135 lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
136 }
137
138 uint8_t prev_mo_len = get_month_length(d.year, d.month - 1);
139 for(i = 0, c = prev_mo_len - day_first + 1; i < day_first; i++, c++) {
140 lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
141 lv_btnmatrix_set_btn_ctrl(calendar->btnm, i + 7, LV_BTNMATRIX_CTRL_DISABLED);
142 }
143
144 for(i = day_first + act_mo_len, c = 1; i < 6 * 7; i++, c++) {
145 lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
146 lv_btnmatrix_set_btn_ctrl(calendar->btnm, i + 7, LV_BTNMATRIX_CTRL_DISABLED);
147 }
148
149 highlight_update(obj);
150
151 /*Reset the focused button if the days changes*/
152 if(lv_btnmatrix_get_selected_btn(calendar->btnm) != LV_BTNMATRIX_BTN_NONE) {
153 lv_btnmatrix_set_selected_btn(calendar->btnm, day_first + 7);
154 }
155
156 lv_obj_invalidate(obj);
157
158 /* The children of the calendar are probably headers.
159 * Notify them to let the headers updated to the new date*/
160 uint32_t child_cnt = lv_obj_get_child_cnt(obj);
161 for(i = 0; i < child_cnt; i++) {
162 lv_obj_t * child = lv_obj_get_child(obj, i);
163 if(child == calendar->btnm) continue;
164 lv_event_send(child, LV_EVENT_VALUE_CHANGED, obj);
165 }
166 }
167
168 /*=====================
169 * Getter functions
170 *====================*/
171
lv_calendar_get_btnmatrix(const lv_obj_t * obj)172 lv_obj_t * lv_calendar_get_btnmatrix(const lv_obj_t * obj)
173 {
174 LV_ASSERT_OBJ(obj, MY_CLASS);
175 const lv_calendar_t * calendar = (lv_calendar_t *)obj;
176 return calendar->btnm;
177 }
178
lv_calendar_get_today_date(const lv_obj_t * obj)179 const lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * obj)
180 {
181 LV_ASSERT_OBJ(obj, MY_CLASS);
182 const lv_calendar_t * calendar = (lv_calendar_t *)obj;
183
184 return &calendar->today;
185 }
186
lv_calendar_get_showed_date(const lv_obj_t * obj)187 const lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * obj)
188 {
189 LV_ASSERT_OBJ(obj, MY_CLASS);
190 const lv_calendar_t * calendar = (lv_calendar_t *)obj;
191
192 return &calendar->showed_date;
193 }
194
lv_calendar_get_highlighted_dates(const lv_obj_t * obj)195 lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * obj)
196 {
197 LV_ASSERT_OBJ(obj, MY_CLASS);
198 lv_calendar_t * calendar = (lv_calendar_t *)obj;
199
200 return calendar->highlighted_dates;
201 }
202
lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj)203 uint16_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj)
204 {
205 LV_ASSERT_OBJ(obj, MY_CLASS);
206 lv_calendar_t * calendar = (lv_calendar_t *)obj;
207
208 return calendar->highlighted_dates_num;
209 }
210
lv_calendar_get_pressed_date(const lv_obj_t * obj,lv_calendar_date_t * date)211 lv_res_t lv_calendar_get_pressed_date(const lv_obj_t * obj, lv_calendar_date_t * date)
212 {
213 LV_ASSERT_OBJ(obj, MY_CLASS);
214 lv_calendar_t * calendar = (lv_calendar_t *)obj;
215
216 uint16_t d = lv_btnmatrix_get_selected_btn(calendar->btnm);
217 if(d == LV_BTNMATRIX_BTN_NONE) {
218 date->year = 0;
219 date->month = 0;
220 date->day = 0;
221 return LV_RES_INV;
222 }
223
224 const char * txt = lv_btnmatrix_get_btn_text(calendar->btnm, lv_btnmatrix_get_selected_btn(calendar->btnm));
225
226 if(txt[1] == 0) date->day = txt[0] - '0';
227 else date->day = (txt[0] - '0') * 10 + (txt[1] - '0');
228
229 date->year = calendar->showed_date.year;
230 date->month = calendar->showed_date.month;
231
232 return LV_RES_OK;
233 }
234
235
236 /**********************
237 * STATIC FUNCTIONS
238 **********************/
239
lv_calendar_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)240 static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
241 {
242 LV_UNUSED(class_p);
243 lv_calendar_t * calendar = (lv_calendar_t *)obj;
244
245 /*Initialize the allocated 'ext'*/
246 calendar->today.year = 2020;
247 calendar->today.month = 1;
248 calendar->today.day = 1;
249
250 calendar->showed_date.year = 2020;
251 calendar->showed_date.month = 1;
252 calendar->showed_date.day = 1;
253
254 calendar->highlighted_dates = NULL;
255 calendar->highlighted_dates_num = 0;
256
257 lv_memset_00(calendar->nums, sizeof(calendar->nums));
258 uint8_t i;
259 uint8_t j = 0;
260 for(i = 0; i < 8 * 7; i++) {
261 /*Every 8th string is "\n"*/
262 if(i != 0 && (i + 1) % 8 == 0) {
263 calendar->map[i] = "\n";
264 }
265 else if(i < 8) {
266 calendar->map[i] = day_names_def[i];
267 }
268 else {
269 calendar->nums[j][0] = 'x';
270 calendar->map[i] = calendar->nums[j];
271 j++;
272 }
273 }
274 calendar->map[8 * 7 - 1] = "";
275
276 calendar->btnm = lv_btnmatrix_create(obj);
277 lv_btnmatrix_set_map(calendar->btnm, calendar->map);
278 lv_btnmatrix_set_btn_ctrl_all(calendar->btnm, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT);
279 lv_obj_add_event_cb(calendar->btnm, draw_part_begin_event_cb, LV_EVENT_DRAW_PART_BEGIN, NULL);
280 lv_obj_set_width(calendar->btnm, lv_pct(100));
281
282 lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
283 lv_obj_set_flex_grow(calendar->btnm, 1);
284
285 lv_calendar_set_showed_date(obj, calendar->showed_date.year, calendar->showed_date.month);
286 lv_calendar_set_today_date(obj, calendar->today.year, calendar->today.month, calendar->today.day);
287
288 lv_obj_add_flag(calendar->btnm, LV_OBJ_FLAG_EVENT_BUBBLE);
289 }
290
draw_part_begin_event_cb(lv_event_t * e)291 static void draw_part_begin_event_cb(lv_event_t * e)
292 {
293 lv_obj_t * obj = lv_event_get_target(e);
294 lv_obj_draw_part_dsc_t * dsc = lv_event_get_param(e);
295 if(dsc->part == LV_PART_ITEMS) {
296 /*Day name styles*/
297 if(dsc->id < 7) {
298 dsc->rect_dsc->bg_opa = LV_OPA_TRANSP;
299 dsc->rect_dsc->border_opa = LV_OPA_TRANSP;
300 }
301 else if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_DISABLED)) {
302 dsc->rect_dsc->bg_opa = LV_OPA_TRANSP;
303 dsc->rect_dsc->border_opa = LV_OPA_TRANSP;
304 dsc->label_dsc->color = lv_palette_main(LV_PALETTE_GREY);
305 }
306
307 if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_HIGHLIGHT)) {
308 dsc->rect_dsc->bg_opa = LV_OPA_40;
309 dsc->rect_dsc->bg_color = lv_theme_get_color_primary(obj);
310 if(lv_btnmatrix_get_selected_btn(obj) == dsc->id) {
311 dsc->rect_dsc->bg_opa = LV_OPA_70;
312 }
313 }
314
315 if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_TODAY)) {
316 dsc->rect_dsc->border_opa = LV_OPA_COVER;
317 dsc->rect_dsc->border_color = lv_theme_get_color_primary(obj);
318 dsc->rect_dsc->border_width += 1;
319 }
320
321 }
322 }
323
324 /**
325 * Get the number of days in a month
326 * @param year a year
327 * @param month a month. The range is basically [1..12] but [-11..0] or [13..24] is also
328 * supported to handle next/prev. year
329 * @return [28..31]
330 */
get_month_length(int32_t year,int32_t month)331 static uint8_t get_month_length(int32_t year, int32_t month)
332 {
333 month--;
334 if(month < 0) {
335 year--; /*Already in the previous year (won't be less then -12 to skip a whole year)*/
336 month = 12 + month; /*`month` is negative, the result will be < 12*/
337 }
338 if(month >= 12) {
339 year++;
340 month -= 12;
341 }
342
343 /*month == 1 is february*/
344 return (month == 1) ? (28 + is_leap_year(year)) : 31 - month % 7 % 2;
345 }
346
347 /**
348 * Tells whether a year is leap year or not
349 * @param year a year
350 * @return 0: not leap year; 1: leap year
351 */
is_leap_year(uint32_t year)352 static uint8_t is_leap_year(uint32_t year)
353 {
354 return (year % 4) || ((year % 100 == 0) && (year % 400)) ? 0 : 1;
355 }
356
357 /**
358 * Get the day of the week
359 * @param year a year
360 * @param month a month [1..12]
361 * @param day a day [1..32]
362 * @return [0..6] which means [Sun..Sat] or [Mon..Sun] depending on LV_CALENDAR_WEEK_STARTS_MONDAY
363 */
get_day_of_week(uint32_t year,uint32_t month,uint32_t day)364 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day)
365 {
366 uint32_t a = month < 3 ? 1 : 0;
367 uint32_t b = year - a;
368
369 #if LV_CALENDAR_WEEK_STARTS_MONDAY
370 uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400) - 1) % 7;
371 #else
372 uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400)) % 7;
373 #endif
374
375 return day_of_week ;
376 }
377
highlight_update(lv_obj_t * obj)378 static void highlight_update(lv_obj_t * obj)
379 {
380 lv_calendar_t * calendar = (lv_calendar_t *)obj;
381 uint16_t i;
382
383 /*Clear all kind of selection*/
384 lv_btnmatrix_clear_btn_ctrl_all(calendar->btnm, LV_CALENDAR_CTRL_TODAY | LV_CALENDAR_CTRL_HIGHLIGHT);
385
386 uint8_t day_first = get_day_of_week(calendar->showed_date.year, calendar->showed_date.month, 1);
387 if(calendar->highlighted_dates) {
388 for(i = 0; i < calendar->highlighted_dates_num; i++) {
389 if(calendar->highlighted_dates[i].year == calendar->showed_date.year &&
390 calendar->highlighted_dates[i].month == calendar->showed_date.month) {
391 lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->highlighted_dates[i].day - 1 + day_first + 7,
392 LV_CALENDAR_CTRL_HIGHLIGHT);
393 }
394 }
395 }
396
397 if(calendar->showed_date.year == calendar->today.year && calendar->showed_date.month == calendar->today.month) {
398 lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->today.day - 1 + day_first + 7, LV_CALENDAR_CTRL_TODAY);
399 }
400 }
401
402 #endif /*LV_USE_CALENDAR*/
403