1use std::cmp;
7
8use crate::iced_core::{Alignment, Length, Padding};
9use crate::widget::{Grid, button, column, grid, icon, row, text};
10use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday};
11
12pub fn calendar<M>(
14 model: &CalendarModel,
15 on_select: impl Fn(NaiveDate) -> M + 'static,
16 on_prev: impl Fn() -> M + 'static,
17 on_next: impl Fn() -> M + 'static,
18) -> Calendar<M> {
19 Calendar {
20 model,
21 on_select: Box::new(on_select),
22 on_prev: Box::new(on_prev),
23 on_next: Box::new(on_next),
24 }
25}
26
27pub fn set_day(date_selected: NaiveDate, day: u32) -> NaiveDate {
28 let current = date_selected.day();
29
30 let new_date = match current.cmp(&day) {
31 cmp::Ordering::Less => date_selected.checked_add_days(Days::new((day - current) as u64)),
32
33 cmp::Ordering::Greater => date_selected.checked_sub_days(Days::new((current - day) as u64)),
34
35 _ => None,
36 };
37
38 if let Some(new) = new_date {
39 new
40 } else {
41 date_selected
42 }
43}
44
45#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
46pub struct CalendarModel {
47 pub selected: NaiveDate,
48 pub visible: NaiveDate,
49}
50
51impl CalendarModel {
52 pub fn now() -> Self {
53 let now = Local::now();
54 let naive_now = NaiveDate::from(now.naive_local());
55 CalendarModel {
56 selected: naive_now,
57 visible: naive_now,
58 }
59 }
60
61 #[inline]
62 pub fn new(selected: NaiveDate, visible: NaiveDate) -> Self {
63 CalendarModel { selected, visible }
64 }
65
66 pub fn show_prev_month(&mut self) {
67 let prev_month_date = self
68 .visible
69 .checked_sub_months(Months::new(1))
70 .expect("valid naivedate");
71
72 self.visible = prev_month_date;
73 }
74
75 pub fn show_next_month(&mut self) {
76 let next_month_date = self
77 .visible
78 .checked_add_months(Months::new(1))
79 .expect("valid naivedate");
80
81 self.visible = next_month_date;
82 }
83
84 #[inline]
85 pub fn set_prev_month(&mut self) {
86 self.show_prev_month();
87 self.selected = self.visible;
88 }
89
90 #[inline]
91 pub fn set_next_month(&mut self) {
92 self.show_next_month();
93 self.selected = self.visible;
94 }
95
96 #[inline]
97 pub fn set_selected_visible(&mut self, selected: NaiveDate) {
98 self.selected = selected;
99 self.visible = self.selected;
100 }
101}
102
103pub struct Calendar<'a, M> {
104 model: &'a CalendarModel,
105 on_select: Box<dyn Fn(NaiveDate) -> M>,
106 on_prev: Box<dyn Fn() -> M>,
107 on_next: Box<dyn Fn() -> M>,
108}
109
110impl<'a, Message> From<Calendar<'a, Message>> for crate::Element<'a, Message>
111where
112 Message: Clone + 'static,
113{
114 fn from(this: Calendar<'a, Message>) -> Self {
115 let date = text(this.model.visible.format("%B %Y").to_string()).size(18);
116
117 let month_controls = row::with_capacity(2)
118 .push(
119 button::icon(icon::from_name("go-previous-symbolic"))
120 .padding([0, 12])
121 .on_press((this.on_prev)()),
122 )
123 .push(
124 button::icon(icon::from_name("go-next-symbolic"))
125 .padding([0, 12])
126 .on_press((this.on_next)()),
127 );
128
129 let mut calendar_grid: Grid<'_, Message> =
131 grid().padding([0, 12].into()).width(Length::Fill);
132
133 let mut first_day_of_week = Weekday::Sun; for _ in 0..7 {
135 calendar_grid = calendar_grid.push(
136 text(first_day_of_week.to_string())
137 .size(12)
138 .width(Length::Fixed(36.0))
139 .align_x(Alignment::Center),
140 );
141
142 first_day_of_week = first_day_of_week.succ();
143 }
144 calendar_grid = calendar_grid.insert_row();
145
146 let monday = get_calender_first(
147 this.model.visible.year(),
148 this.model.visible.month(),
149 first_day_of_week,
150 );
151 let mut day_iter = monday.iter_days();
152 for i in 0..42 {
153 if i > 0 && i % 7 == 0 {
154 calendar_grid = calendar_grid.insert_row();
155 }
156
157 let date = day_iter.next().unwrap();
158 let is_currently_viewed_month = date.month() == this.model.visible.month()
159 && date.year_ce() == this.model.visible.year_ce();
160 let is_currently_selected_month = date.month() == this.model.selected.month()
161 && date.year_ce() == this.model.selected.year_ce();
162 let is_currently_selected_day =
163 date.day() == this.model.selected.day() && is_currently_selected_month;
164
165 calendar_grid = calendar_grid.push(date_button(
166 date,
167 is_currently_viewed_month,
168 is_currently_selected_day,
169 &this.on_select,
170 ));
171 }
172
173 let content_list = column::with_children(vec![
174 row::with_children(vec![
175 date.into(),
176 crate::widget::Space::with_width(Length::Fill).into(),
177 month_controls.into(),
178 ])
179 .padding([12, 20])
180 .into(),
181 calendar_grid.into(),
182 padded_control(crate::widget::divider::horizontal::default()).into(),
183 ])
184 .width(315)
185 .padding([8, 0]);
186
187 Self::new(content_list)
188 }
189}
190
191fn date_button<Message>(
192 date: NaiveDate,
193 is_currently_viewed_month: bool,
194 is_currently_selected_day: bool,
195 on_select: &dyn Fn(NaiveDate) -> Message,
196) -> crate::widget::Button<'static, Message> {
197 let style = if is_currently_selected_day {
198 button::ButtonClass::Suggested
199 } else {
200 button::ButtonClass::Text
201 };
202
203 let button = button::custom(text(format!("{}", date.day())).center())
204 .class(style)
205 .height(Length::Fixed(36.0))
206 .width(Length::Fixed(36.0));
207
208 if is_currently_viewed_month {
209 button.on_press((on_select)(set_day(date, date.day())))
210 } else {
211 button
212 }
213}
214
215#[must_use]
217pub fn get_calender_first(year: i32, month: u32, from_weekday: Weekday) -> NaiveDate {
218 let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
219 let num_days = (date.weekday() as u32 + 7 - from_weekday as u32) % 7; date.checked_sub_days(Days::new(num_days as u64)).unwrap()
221}
222
223fn padded_control<'a, Message>(
225 content: impl Into<crate::Element<'a, Message>>,
226) -> crate::widget::container::Container<'a, Message, crate::Theme, crate::Renderer> {
227 crate::widget::container(content)
228 .padding(menu_control_padding())
229 .width(Length::Fill)
230}
231
232#[inline]
233fn menu_control_padding() -> Padding {
234 let guard = crate::theme::THEME.lock().unwrap();
235 let cosmic = guard.cosmic();
236 [cosmic.space_xxs(), cosmic.space_m()].into()
237}