1use alloc::format;
16
17use crate::consts::*;
18use crate::i18n;
19use crate::keys::*;
20use crate::{Sp, Ui};
21
22fn part(id: u32, k: u32) -> u32 {
23 id.wrapping_mul(8).wrapping_add(k) | 0x8000_0000
24}
25
26pub fn days_in_month(y: i32, m: u32) -> u32 {
28 match m {
29 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
30 4 | 6 | 9 | 11 => 30,
31 _ => {
32 if (y % 4 == 0 && y % 100 != 0) || y % 400 == 0 {
33 29
34 } else {
35 28
36 }
37 }
38 }
39}
40
41pub fn days_from_civil(y: i32, m: u32, d: u32) -> i64 {
43 let y = if m <= 2 { y - 1 } else { y } as i64;
44 let era = if y >= 0 { y } else { y - 399 } / 400;
45 let yoe = y - era * 400;
46 let mp = (m as i64 + 9) % 12;
47 let doy = (153 * mp + 2) / 5 + d as i64 - 1;
48 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
49 era * 146097 + doe - 719468
50}
51
52pub fn weekday(y: i32, m: u32, d: u32) -> u32 {
54 (days_from_civil(y, m, d) + 3).rem_euclid(7) as u32
56}
57
58impl Ui {
59 #[cfg_attr(jfgui_dev, track_caller)]
61 pub fn time_box(&mut self, id: u32, hour: &mut i32, minute: &mut i32) -> u32 {
62 let _src = self.src_scope();
63 let (h0, m0) = (*hour, *minute);
64 self.box_begin(0, BOX_H | BOX_XCENTER, 4);
65 self.next_size(64, 0);
66 let e1 = self.number_box(part(id, 1), hour, &Range::new(0, 23, 1, 0));
67 self.text(TS_BODY_STRONG, ":");
68 self.next_size(64, 0);
69 let e2 = self.number_box(part(id, 2), minute, &Range::new(0, 59, 1, 0));
70 self.box_end();
71 let ev = (e1 | e2) & !EV_ACT;
72 if *hour != h0 || *minute != m0 { ev | EV_ACT } else { ev }
73 }
74
75 #[cfg_attr(jfgui_dev, track_caller)]
78 pub fn weekday_toggles(&mut self, id: u32, mask: &mut u32) -> u32 {
79 let _src = self.src_scope();
80 let old = *mask;
81 self.box_begin(0, BOX_H | BOX_XCENTER | BOX_WRAP, 4);
82 for d in 0..7u32 {
83 let on = *mask & (1 << d) != 0;
84 self.next_size(40, 0);
85 self.next_flags(ST_COMPACT | if on { ST_CHECKED | ST_ACCENT } else { 0 });
86 if self.button(part(id, 1 + d), i18n::weekday_short(d)) & EV_ACT != 0 {
87 *mask ^= 1 << d;
88 self.repass();
89 }
90 }
91 self.box_end();
92 if *mask != old { EV_ACT } else { 0 }
93 }
94
95 #[cfg_attr(jfgui_dev, track_caller)]
97 pub fn date_picker(&mut self, id: u32, y: &mut i32, m: &mut u32, d: &mut u32) -> u32 {
98 let _src = self.src_scope();
99 let label = i18n::fmt_date(&datetime::Civil { year: *y, month: *m as u8, day: *d as u8, ..Default::default() }, i18n::DATE_SHORT);
101 let fw = i18n::locale().first_weekday();
102 self.next_icon(IC_CALENDAR);
103 let ev = self.dropdown_button(id, &label);
104 let mut out = ev & !EV_ACT;
105 let vid = part(id, 7);
107 if ev & EV_OPEN == 0 {
108 self.slot(vid).scrollx = *y * 12 + *m as i32 - 1;
109 }
110 if self.flyout_begin(id, 0) {
111 let mut view = self.slot(vid).scrollx;
112 let (vy, vm) = (view.div_euclid(12), (view.rem_euclid(12) + 1) as u32);
113 self.box_begin(0, BOX_H | BOX_XCENTER, 4);
115 self.next_icon(IC_CHEV_LEFT);
116 self.next_flags(ST_SUBTLE | ST_ICON_ONLY);
117 if self.button(part(id, 1), i18n::tr("Vorheriger Monat")) & EV_ACT != 0 {
118 view -= 1;
119 }
120 self.next_size(-1, 0);
121 let title = format!("{} {}", i18n::month_name(vm), vy);
122 self.text(TS_BODY_STRONG | TX_CENTER, &title);
123 self.next_icon(IC_CHEV_RIGHT);
124 self.next_flags(ST_SUBTLE | ST_ICON_ONLY);
125 if self.button(part(id, 2), i18n::tr("Nächster Monat")) & EV_ACT != 0 {
126 view += 1;
127 }
128 self.box_end();
129 if view != self.slot(vid).scrollx {
130 self.slot(vid).scrollx = view;
131 self.repass();
132 }
133 let cw = self.dp(36);
135 let ch = self.dp(32);
136 let grid = self.place(cw * 7, ch * 7);
137 let t3 = self.color(T_TEXT3);
138 for c in 0..7u32 {
139 let s = i18n::weekday_short((c + fw) % 7);
140 self.ts_font(TS_CAPTION);
141 let tw = self.text.width(s);
142 let x = grid[0] + cw * c as i32;
143 crate::stock::text_at(self, TS_CAPTION, x + ((cw - tw) >> 1), [x, grid[1], cw, ch], s, t3, 0);
144 }
145 let first = ((weekday(vy, vm, 1) + 7 - fw) % 7) as i32;
146 let dim = days_in_month(vy, vm) as i32;
147 let today = plat::date().0;
148 let sh = self.style.shell();
149 for cell in 0..42i32 {
150 let day = cell - first + 1;
151 let (cy, cm, cd, other) = if day < 1 {
152 let (py, pm) = if vm == 1 { (vy - 1, 12) } else { (vy, vm - 1) };
153 (py, pm, (days_in_month(py, pm) as i32 + day) as u32, true)
154 } else if day > dim {
155 let (ny, nm) = if vm == 12 { (vy + 1, 1) } else { (vy, vm + 1) };
156 (ny, nm, (day - dim) as u32, true)
157 } else {
158 (vy, vm, day as u32, false)
159 };
160 let r = [grid[0] + cw * (cell % 7), grid[1] + ch * (1 + cell / 7), cw, ch];
161 let did = ((id ^ 0x3C3C).wrapping_mul(64).wrapping_add(cell as u32)) | 0x8000_0000;
162 let dev = self.hit_rect(did, r[0], r[1], r[2], r[3], 0);
163 let selected = cy == *y && cm == *m && cd == *d;
164 let is_today = today.year as i32 == cy && today.month as u32 == cm && today.day as u32 == cd;
165 let s = format!("{}", cd);
166 let mut sp = Sp { ev: dev, value: selected as i32, value2: is_today as i32, flags: other as u32, label: Some(&s), ..Sp::default() };
167 sp.place(r);
168 sh.calendar_day(self, &sp);
169 if selected {
170 self.wp.ctx.last_state |= SS_SELECTED as u32;
171 }
172 self.sem(K_BUTTON, did, Some(&s), cd as i32);
173 if dev & EV_ACT != 0 {
174 *y = cy;
175 *m = cm;
176 *d = cd;
177 out |= EV_ACT;
178 self.popup_close();
179 self.repass();
180 }
181 }
182 self.next_flags(ST_SUBTLE | ST_COMPACT);
184 if self.button(part(id, 3), i18n::tr("Heute")) & EV_ACT != 0 {
185 *y = today.year as i32;
186 *m = today.month as u32;
187 *d = today.day as u32;
188 out |= EV_ACT;
189 self.popup_close();
190 self.repass();
191 }
192 }
193 self.flyout_end();
194 out
195 }
196}