1use chrono::{Datelike, NaiveDate, Weekday};
2
3use crate::prelude::*;
4
5pub struct Datepicker {
7 view_date: Signal<NaiveDate>,
8 on_select: Option<Box<dyn Fn(&mut EventContext, NaiveDate)>>,
9}
10
11const MONTHS: [&str; 12] = [
12 "January",
13 "February",
14 "March",
15 "April",
16 "May",
17 "June",
18 "July",
19 "August",
20 "September",
21 "October",
22 "November",
23 "December",
24];
25
26const DAYS_HEADER: [&str; 7] =
27 ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
28
29pub(crate) enum DatepickerEvent {
30 IncrementMonth,
31 DecrementMonth,
32 SelectMonth(usize),
33
34 IncrementYear,
35 DecrementYear,
36 SelectYear(String),
37
38 SelectDate(NaiveDate),
39}
40
41impl Datepicker {
42 fn set_view_date(&mut self, year: i32, month: u32, day: u32) {
43 self.view_date.set(NaiveDate::from_ymd_opt(year, month, day).unwrap());
44 }
45
46 fn shift_month(&mut self, delta: i32) {
47 let view_date = self.view_date.get();
48 let mut year = view_date.year();
49 let mut month = view_date.month() as i32 + delta;
50
51 if month < 1 {
52 year -= 1;
53 month += 12;
54 } else if month > 12 {
55 year += 1;
56 month -= 12;
57 }
58
59 self.set_view_date(year, month as u32, view_date.day());
60 }
61
62 fn shift_year(&mut self, delta: i32) {
63 let view_date = self.view_date.get();
64 self.set_view_date(view_date.year() + delta, view_date.month(), view_date.day());
65 }
66
67 fn first_day_of_month(year: i32, month: u32) -> Option<Weekday> {
68 NaiveDate::from_ymd_opt(year, month, 1).map(|date| date.weekday())
69 }
70
71 fn last_day_of_month(year: i32, month: u32) -> Option<u32> {
72 if month == 12 {
73 NaiveDate::from_ymd_opt(year + 1, 1, 1)
74 } else {
75 NaiveDate::from_ymd_opt(year, month + 1, 1)
76 }
77 .map(|date| {
78 date.signed_duration_since(NaiveDate::from_ymd_opt(year, month, 1).unwrap()).num_days()
79 as u32
80 })
81 }
82
83 fn view_month_info(view_date: &NaiveDate, month_offset: i32) -> (Weekday, u32) {
85 let month = view_date.month();
86 let mut year = view_date.year();
87
88 let mut month = month as i32 + month_offset;
89
90 if month < 1 {
91 year -= 1;
92 month += 12;
93 } else if month > 12 {
94 year += 1;
95 month -= 12;
96 }
97
98 let month = month as u32;
99
100 (
101 Self::first_day_of_month(year, month).unwrap(),
102 Self::last_day_of_month(year, month).unwrap(),
103 )
104 }
105
106 fn get_day_number(y: u32, x: u32, view_date: &NaiveDate) -> (u32, bool) {
107 let (_, days_prev_month) = Self::view_month_info(view_date, -1);
108 let (first_day_this_month, days_this_month) = Self::view_month_info(view_date, 0);
109
110 let mut fdtm_i = first_day_this_month as usize as u32;
111 if fdtm_i == 0 {
112 fdtm_i = 7;
113 }
114
115 if y == 0 {
116 if x < fdtm_i {
117 (days_prev_month - (fdtm_i - x - 1), true)
118 } else {
119 (x - fdtm_i + 1, false)
120 }
121 } else {
122 let day_number = y * 7 + x - fdtm_i + 1;
123 if day_number > days_this_month {
124 (day_number - days_this_month, true)
125 } else {
126 (day_number, false)
127 }
128 }
129 }
130
131 pub fn new<R, D>(cx: &mut Context, date: R) -> Handle<Self>
133 where
134 R: Res<D> + Clone + 'static,
135 D: Datelike + Clone + 'static,
136 {
137 let selected_date = date.get_value(cx);
138 let selected_date_signal = date.to_signal(cx);
139 let initial_view_date =
140 NaiveDate::from_ymd_opt(selected_date.year(), selected_date.month(), 1).unwrap();
141 let view_date = Signal::new(initial_view_date);
142 let month_options =
143 Signal::new(MONTHS.iter().map(|m| Localized::new(m)).collect::<Vec<_>>());
144 let selected_month = view_date.map(|date| date.month() as usize - 1);
145
146 Self { view_date, on_select: None }.build(cx, move |cx| {
147 HStack::new(cx, |cx| {
148 Spinbox::custom(cx, move |cx| {
149 PickList::new(cx, month_options, selected_month, false)
150 .on_select(|ex, index| ex.emit(DatepickerEvent::SelectMonth(index)))
151 .width(Stretch(1.0))
152 })
153 .width(Pixels(140.0))
154 .on_increment(|ex| ex.emit(DatepickerEvent::IncrementMonth))
155 .on_decrement(|ex| ex.emit(DatepickerEvent::DecrementMonth));
156 Spinbox::custom(cx, |cx| {
157 let view_date = cx.data::<Datepicker>().view_date;
158 let year = view_date.map(|date| date.year());
159 Textbox::new(cx, year)
160 .width(Stretch(1.0))
161 .padding(Pixels(1.0))
162 .on_edit(|ex, v| ex.emit(DatepickerEvent::SelectYear(v)))
163 })
164 .width(Pixels(100.0))
165 .icons(SpinboxIcons::PlusMinus)
166 .on_increment(|ex| ex.emit(DatepickerEvent::IncrementYear))
167 .on_decrement(|ex| ex.emit(DatepickerEvent::DecrementYear));
168 })
169 .class("datepicker-header");
170
171 Divider::new(cx);
172
173 VStack::new(cx, move |cx| {
174 HStack::new(cx, |cx| {
176 for h in DAYS_HEADER {
177 Label::new(cx, Localized::new(h).map(|day| day[0..2].to_string()))
178 .class("datepicker-calendar-header");
179 }
180 })
181 .class("datepicker-calendar-headers");
182
183 VStack::new(cx, move |cx| {
185 for y in 0..6 {
186 HStack::new(cx, |cx| {
187 for x in 0..7 {
188 let selected_date = selected_date_signal;
189 let view_date = cx.data::<Datepicker>().view_date;
190 Label::new(cx, "").bind(view_date, move |handle| {
191 let view_date = view_date.get();
192 let selected_date = selected_date;
193
194 let (day_number, disabled) =
195 Self::get_day_number(y, x, &view_date);
196
197 handle.bind(selected_date, move |handle| {
198 let selected_date = selected_date.get();
199
200 handle
201 .text(&day_number.to_string())
202 .class("datepicker-calendar-day")
203 .navigable(!disabled)
204 .toggle_class(
205 "datepicker-calendar-day-disabled",
206 disabled,
207 )
208 .on_press(move |ex| {
209 if !disabled {
210 ex.emit(DatepickerEvent::SelectDate(
211 NaiveDate::from_ymd_opt(
212 view_date.year(),
213 view_date.month(),
214 day_number,
215 )
216 .unwrap(),
217 ))
218 }
219 })
220 .checked(
221 !disabled
222 && selected_date.day() == day_number
223 && selected_date.month() == view_date.month()
224 && selected_date.year() == view_date.year(),
225 );
226 });
227 });
228 }
229 });
230 }
231 })
232 .width(Pixels(32.0 * 7.0))
234 .height(Pixels(32.0 * 6.0));
235 })
236 .class("datepicker-calendar");
237 })
238 }
239}
240
241impl View for Datepicker {
242 fn element(&self) -> Option<&'static str> {
243 Some("datepicker")
244 }
245
246 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
247 event.map(|e, _| match e {
248 DatepickerEvent::IncrementMonth => {
249 self.shift_month(1);
250 }
251
252 DatepickerEvent::DecrementMonth => {
253 self.shift_month(-1);
254 }
255
256 DatepickerEvent::SelectMonth(month) => {
257 let view_date = self.view_date.get();
258 self.set_view_date(view_date.year(), *month as u32 + 1, view_date.day());
259 }
260
261 DatepickerEvent::IncrementYear => {
262 self.shift_year(1);
263 }
264
265 DatepickerEvent::DecrementYear => {
266 self.shift_year(-1);
267 }
268
269 DatepickerEvent::SelectYear(year) => {
270 if let Ok(year) = year.parse::<i32>() {
271 let view_date = self.view_date.get();
272 self.set_view_date(year, view_date.month(), view_date.day());
273 }
274 }
275
276 DatepickerEvent::SelectDate(date) => {
277 if let Some(callback) = &self.on_select {
278 (callback)(cx, *date);
279 }
280 }
281 })
282 }
283}
284
285impl Handle<'_, Datepicker> {
286 pub fn on_select<F: 'static + Fn(&mut EventContext, NaiveDate)>(self, callback: F) -> Self {
288 self.modify(|datepicker: &mut Datepicker| datepicker.on_select = Some(Box::new(callback)))
289 }
290}