Skip to main content

ui/
datepick.rs

1// datepick -- date and time input
2//
3//   time_box(id, &mut hour, &mut minute) -> EV    two number boxes "HH : MM" (EV_ACT changed)
4//   weekday_toggles(id, &mut mask) -> EV          seven toggle buttons, bit 0 = Monday ..
5//                                                 bit 6 = Sunday (EV_ACT changed); they wrap
6//                                                 where the row is narrower (BOX_WRAP)
7//   date_picker(id, &mut y, &mut m, &mut d) -> EV a button with the date (the user's format,
8//                                                 i18n::fmt_date) that opens a month calendar
9//                                                 starting on the locale's first weekday
10//                                                 (EV_ACT when a day was picked)
11//   civil helpers: days_in_month(y, m), weekday(y, m, d) (0 = Monday)
12//
13// Ids: parts (id << 3 | k) | 0x80000000; calendar days ((id ^ 0x3C3C) << 6 | cell) | 0x80000000.
14
15use 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
26/// days of month m (1..12) of year y (proleptic Gregorian)
27pub 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
41/// days since 1970-01-01 of a civil date (Howard Hinnant's algorithm)
42pub 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
52/// weekday of a civil date, 0 = Monday .. 6 = Sunday
53pub fn weekday(y: i32, m: u32, d: u32) -> u32 {
54    // 1970-01-01 was a Thursday (3)
55    (days_from_civil(y, m, d) + 3).rem_euclid(7) as u32
56}
57
58impl Ui {
59    /// ui_time_box(id, &mut hour, &mut minute) -> EV_* (EV_ACT changed)
60    #[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    /// ui_weekday_toggles(id, &mut mask) -> EV_* (EV_ACT changed); bit 0 = Monday.  The
76    /// buttons wrap where the row has less room than the seven of them (a narrow card)
77    #[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    /// ui_date_picker(id, &mut y, &mut m, &mut d) -> EV_* (EV_ACT when a day was picked)
96    #[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        // the date in the user's format; the week starts on the locale's first day
100        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        // the month shown: slot scrollx = year * 12 + month - 1 (set when the popup opens)
106        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            // header: previous, month name, next
114            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            // weekday header and the 6 x 7 day grid
134            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            // today
183            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}