1use alloc::format;
25
26use crate::consts::*;
27use crate::i18n;
28use crate::keys::*;
29use crate::{Sp, Ui};
30
31pub const CAL_DAY: u32 = 0;
33pub const CAL_WEEK: u32 = 1;
36pub const CAL_MONTH: u32 = 2;
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub struct DateRange {
44 pub mode: u32,
45 pub min: (i32, u32, u32),
46 pub max: (i32, u32, u32),
47}
48
49impl DateRange {
50 pub const DAYS: DateRange = DateRange::new(CAL_DAY, (1, 1, 1), (9999, 12, 31));
52
53 pub const fn new(mode: u32, min: (i32, u32, u32), max: (i32, u32, u32)) -> DateRange {
54 DateRange { mode, min, max }
55 }
56
57 fn key(&self, (y, m, d): (i32, u32, u32)) -> i64 {
59 match self.mode {
60 CAL_WEEK => days(y, m, d) - wday(y, m, d) as i64,
61 CAL_MONTH => y as i64 * 12 + m as i64 - 1,
62 _ => days(y, m, d),
63 }
64 }
65
66 fn has(&self, date: (i32, u32, u32)) -> bool {
68 let k = self.key(date);
69 self.key(self.min) <= k && k <= self.key(self.max)
70 }
71}
72
73fn part(id: u32, k: u32) -> u32 {
74 id.wrapping_mul(8).wrapping_add(k) | 0x8000_0000
75}
76
77fn cell_id(id: u32, cell: u32) -> u32 {
78 ((id ^ 0x3C3C).wrapping_mul(64).wrapping_add(cell)) | 0x8000_0000
79}
80
81fn dim(y: i32, m: u32) -> u32 {
83 datetime::days_in_month(y, m.clamp(1, 12) as u8) as u32
84}
85
86fn days(y: i32, m: u32, d: u32) -> i64 {
87 datetime::days_from_civil(y, m as u8, d.min(31) as u8)
88}
89
90fn wday(y: i32, m: u32, d: u32) -> u32 {
91 datetime::weekday(y, m as u8, d.min(31) as u8) as u32
92}
93
94fn civil(z: i64) -> (i32, u32, u32) {
95 let (y, m, d) = datetime::civil_from_days(z);
96 (y, m as u32, d as u32)
97}
98
99impl Ui {
100 #[cfg_attr(jfgui_dev, track_caller)]
102 pub fn time_box(&mut self, id: u32, hour: &mut i32, minute: &mut i32) -> u32 {
103 let _src = self.src_scope();
104 let (h0, m0) = (*hour, *minute);
105 self.box_begin(0, BOX_H | BOX_XCENTER, 4);
106 self.next_size(64, 0);
107 let e1 = self.number_box(part(id, 1), hour, &Range::new(0, 23, 1, 0));
108 self.text(TS_BODY_STRONG, ":");
109 self.next_size(64, 0);
110 let e2 = self.number_box(part(id, 2), minute, &Range::new(0, 59, 1, 0));
111 self.box_end();
112 let ev = (e1 | e2) & !EV_ACT;
113 if *hour != h0 || *minute != m0 { ev | EV_ACT } else { ev }
114 }
115
116 #[cfg_attr(jfgui_dev, track_caller)]
119 pub fn weekday_toggles(&mut self, id: u32, mask: &mut u32) -> u32 {
120 let _src = self.src_scope();
121 let old = *mask;
122 self.box_begin(0, BOX_H | BOX_XCENTER | BOX_WRAP, 4);
123 for d in 0..7u32 {
124 let on = *mask & (1 << d) != 0;
125 self.next_size(40, 0);
126 self.next_flags(ST_COMPACT | if on { ST_CHECKED | ST_ACCENT } else { 0 });
127 if self.button(part(id, 1 + d), i18n::weekday_short(d)) & EV_ACT != 0 {
128 *mask ^= 1 << d;
129 self.repass();
130 }
131 }
132 self.box_end();
133 if *mask != old { EV_ACT } else { 0 }
134 }
135
136 #[cfg_attr(jfgui_dev, track_caller)]
140 pub fn date_picker(&mut self, id: u32, y: &mut i32, m: &mut u32, d: &mut u32) -> u32 {
141 let _src = self.src_scope();
142 let label = i18n::fmt_date(&datetime::Civil { year: *y, month: *m as u8, day: *d as u8, ..Default::default() }, i18n::DATE_SHORT);
144 self.next_icon(IC_CALENDAR);
145 let ev = self.dropdown_button(id, &label);
146 let mut out = ev & !EV_ACT;
147 if ev & EV_OPEN == 0 {
149 self.slot(part(id, 7)).aux = 0;
150 }
151 if self.flyout_begin(id, 0) {
152 if self.calendar(id, y, m, d, &DateRange::DAYS) & EV_ACT != 0 {
153 out |= EV_ACT;
154 self.popup_close_id(id);
155 }
156 self.next_flags(ST_SUBTLE | ST_COMPACT);
158 if self.button(part(id, 3), i18n::tr("Heute")) & EV_ACT != 0 {
159 let today = plat::date().0;
160 *y = today.year as i32;
161 *m = today.month as u32;
162 *d = today.day as u32;
163 out |= EV_ACT;
164 self.popup_close_id(id);
165 self.repass();
166 }
167 }
168 self.flyout_end();
169 out
170 }
171
172 #[cfg_attr(jfgui_dev, track_caller)]
180 pub fn calendar(&mut self, id: u32, y: &mut i32, m: &mut u32, d: &mut u32, range: &DateRange) -> u32 {
181 let _src = self.src_scope();
182 let months = range.mode == CAL_MONTH;
183 let vid = part(id, 7);
187 let now = days(*y, *m, *d) as i32;
188 let tag = range.mode as i32 + 1;
189 {
190 let s = self.slot(vid);
191 if s.aux != tag || s.scrolly != now {
192 s.scrollx = if months { *y } else { *y * 12 + *m as i32 - 1 };
193 s.scrolly = now;
194 s.aux = tag;
195 }
196 }
197 let mut view = self.slot(vid).scrollx;
198 let (vy, vm) = if months { (view, 1) } else { (view.div_euclid(12), (view.rem_euclid(12) + 1) as u32) };
199 let (before, after) = if months {
202 (range.key((vy - 1, 12, 31)) < range.key(range.min), range.key((vy + 1, 1, 1)) > range.key(range.max))
203 } else {
204 let (py, pm) = if vm == 1 { (vy - 1, 12) } else { (vy, vm - 1) };
205 let (ny, nm) = if vm == 12 { (vy + 1, 1) } else { (vy, vm + 1) };
206 (range.key((py, pm, dim(py, pm))) < range.key(range.min), range.key((ny, nm, 1)) > range.key(range.max))
207 };
208 let prev = if months { i18n::tr("Vorheriges Jahr") } else { i18n::tr("Vorheriger Monat") };
209 let next = if months { i18n::tr("Nächstes Jahr") } else { i18n::tr("Nächster Monat") };
210 self.box_begin(0, BOX_H | BOX_XCENTER, 4);
211 self.next_icon(IC_CHEV_LEFT);
212 self.next_flags(ST_SUBTLE | ST_ICON_ONLY | if before { ST_DISABLED } else { 0 });
213 if self.button(part(id, 1), prev) & EV_ACT != 0 {
214 view -= 1;
215 }
216 self.next_size(-1, 0);
217 let title = if months { format!("{vy}") } else { format!("{} {}", i18n::month_name(vm), vy) };
218 self.text(TS_BODY_STRONG | TX_CENTER, &title);
219 self.next_icon(IC_CHEV_RIGHT);
220 self.next_flags(ST_SUBTLE | ST_ICON_ONLY | if after { ST_DISABLED } else { 0 });
221 if self.button(part(id, 2), next) & EV_ACT != 0 {
222 view += 1;
223 }
224 self.box_end();
225 if view != self.slot(vid).scrollx {
226 self.slot(vid).scrollx = view;
227 self.repass();
228 }
229 let picked = if months { self.cal_months(id, vy, (*y, *m, *d), range) } else { self.cal_days(id, vy, vm, (*y, *m, *d), range) };
230 match picked {
231 Some(date) => {
232 (*y, *m, *d) = date;
233 self.repass();
234 EV_ACT
235 }
236 None => 0,
237 }
238 }
239
240 fn cal_days(&mut self, id: u32, vy: i32, vm: u32, value: (i32, u32, u32), range: &DateRange) -> Option<(i32, u32, u32)> {
243 let weeks = range.mode == CAL_WEEK;
244 let fw = if weeks { 0 } else { i18n::locale().first_weekday() };
246 let cw = self.dp(36);
247 let ch = self.dp(32);
248 let x0 = if weeks { cw } else { 0 };
250 let grid = self.place(x0 + cw * 7, ch * 7);
251 let t3 = self.color(T_TEXT3);
252 for c in 0..7u32 {
253 let s = i18n::weekday_short((c + fw) % 7);
254 self.ts_font(TS_CAPTION);
255 let tw = self.text.width(s);
256 let x = grid[0] + x0 + cw * c as i32;
257 crate::stock::text_at(self, TS_CAPTION, x + ((cw - tw) >> 1), [x, grid[1], cw, ch], s, t3, 0);
258 }
259 let start = days(vy, vm, 1) - ((wday(vy, vm, 1) + 7 - fw) % 7) as i64;
261 let today = plat::date().0;
262 let today = (today.year as i32, today.month as u32, today.day as u32);
263 let sel_week = range.key(value);
264 let sh = self.style.shell();
265 let mut picked = None;
266 for row in 0..6i32 {
267 let ry = grid[1] + ch * (1 + row);
268 let monday = civil(start + row as i64 * 7);
269 let (wev, wsel) = if weeks {
271 let rid = cell_id(id, row as u32);
272 let on = range.has(monday);
273 let ev = self.hit_rect(rid, grid[0], ry, x0 + cw * 7, ch, if on { 0 } else { HB_DISABLED });
274 let (wy, wn) = datetime::iso_week(monday.0, monday.1 as u8, monday.2 as u8);
275 let sel = range.key(monday) == sel_week;
276 if sel {
277 self.wp.ctx.last_state |= SS_SELECTED as u32;
278 }
279 let name = format!("{wy}-W{wn:02}");
280 self.sem(K_BUTTON, rid, Some(&name), wn as i32);
281 let band = if sel { self.color(T_ACCENT) } else if ev & EV_HOVER != 0 { self.color(T_SUBTLE_HOVER) } else { 0 };
283 if band != 0 {
284 self.cmd_rrect(grid[0], ry + self.dp(2), x0 + cw * 7, ch - self.dp(4), (ch - self.dp(4)) >> 1, 0, band, 0, 0);
285 }
286 let num = format!("{wn}");
287 self.ts_font(TS_CAPTION);
288 let tw = self.text.width(&num);
289 let c = if sel { self.color(T_ON_ACCENT) } else { t3 };
290 crate::stock::text_at(self, TS_CAPTION, grid[0] + ((cw - tw) >> 1), [grid[0], ry, cw, ch], &num, c, 0);
291 if ev & EV_ACT != 0 && on {
292 picked = Some(monday);
293 }
294 (ev & !EV_ACT, sel)
295 } else {
296 (0, false)
297 };
298 for col in 0..7i32 {
299 let cell = (row * 7 + col) as u32;
300 let date = civil(start + cell as i64);
301 let other = (date.0, date.1) != (vy, vm);
302 let on = range.has(date);
303 let r = [grid[0] + x0 + cw * col, ry, cw, ch];
304 let s = format!("{}", date.2);
305 let (dev, selected) = if weeks {
306 (wev, wsel)
307 } else {
308 let did = cell_id(id, cell);
309 let dev = self.hit_rect(did, r[0], r[1], r[2], r[3], if on { 0 } else { HB_DISABLED });
310 let selected = date == value;
311 if selected {
312 self.wp.ctx.last_state |= SS_SELECTED as u32;
313 }
314 self.sem(K_BUTTON, did, Some(&s), date.2 as i32);
315 if dev & EV_ACT != 0 && on {
316 picked = Some(date);
317 }
318 (dev, selected)
319 };
320 let mut sp = Sp { ev: dev, value: selected as i32, value2: (date == today) as i32, flags: other as u32, label: Some(&s), ..Sp::default() };
321 sp.state = if on { 0 } else { 3 };
322 sp.place(r);
323 sh.calendar_day(self, &sp);
324 }
325 }
326 picked
327 }
328
329 fn cal_months(&mut self, id: u32, vy: i32, value: (i32, u32, u32), range: &DateRange) -> Option<(i32, u32, u32)> {
331 let (cw, ch) = (self.dp(36) * 7 / 3, self.dp(48));
332 let grid = self.place(cw * 3, ch * 4);
333 let today = plat::date().0;
334 let sh = self.style.shell();
335 let mut picked = None;
336 for k in 0..12u32 {
337 let date = (vy, k + 1, 1);
338 let on = range.has(date);
339 let r = [grid[0] + cw * (k % 3) as i32, grid[1] + ch * (k / 3) as i32, cw, ch];
340 let mid = cell_id(id, k);
341 let ev = self.hit_rect(mid, r[0], r[1], r[2], r[3], if on { 0 } else { HB_DISABLED });
342 let selected = (value.0, value.1) == (vy, k + 1);
343 if selected {
344 self.wp.ctx.last_state |= SS_SELECTED as u32;
345 }
346 let name = format!("{} {vy}", i18n::month_name(k + 1));
347 self.sem(K_BUTTON, mid, Some(&name), (k + 1) as i32);
348 let is_now = (today.year as i32, today.month as u32) == (vy, k + 1);
349 let mut sp = Sp { ev, value: selected as i32, value2: is_now as i32, label: Some(i18n::month_short(k + 1)), ..Sp::default() };
350 sp.state = if on { 0 } else { 3 };
351 sp.place(r);
352 sh.calendar_day(self, &sp);
353 if ev & EV_ACT != 0 && on {
354 picked = Some(date);
355 }
356 }
357 picked
358 }
359}