1use crate::calendar::event::CalOutcome;
2use crate::calendar::selection::{NoSelection, RangeSelection, SingleSelection};
3use crate::calendar::{CalendarSelection, MonthState};
4use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday};
5use rat_event::ConsumedEvent;
6use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
7use rat_reloc::RelocatableState;
8use ratatui::layout::Rect;
9use std::array;
10use std::cell::RefCell;
11use std::ops::RangeInclusive;
12use std::rc::Rc;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum TodayPolicy {
17 Index(usize),
20 Year,
23}
24
25impl Default for TodayPolicy {
26 fn default() -> Self {
27 Self::Index(0)
28 }
29}
30
31#[derive(Debug, Clone)]
37pub struct CalendarState<const N: usize, Selection> {
38 step: usize,
50
51 home: TodayPolicy,
53
54 primary_idx: usize,
59
60 pub months: [MonthState<Selection>; N],
62
63 pub selection: Rc<RefCell<Selection>>,
65
66 pub focus: FocusFlag,
68}
69
70impl<const N: usize, Selection> Default for CalendarState<N, Selection>
71where
72 Selection: Default,
73{
74 fn default() -> Self {
75 let selection = Rc::new(RefCell::new(Selection::default()));
76 let focus = FocusFlag::new();
77
78 Self {
79 step: 1,
80 months: array::from_fn(|_| {
81 let mut state = MonthState::new();
82 state.selection = selection.clone();
83 state.container = Some(focus.clone());
84 state
85 }),
86 selection,
87 primary_idx: Default::default(),
88 focus,
89 home: Default::default(),
90 }
91 }
92}
93
94impl<const N: usize, Selection> HasFocus for CalendarState<N, Selection> {
95 fn build(&self, builder: &mut FocusBuilder) {
96 let tag = builder.start(self);
97 for (i, v) in self.months.iter().enumerate() {
98 if i == self.primary_idx {
99 builder.widget(v); } else {
101 builder.widget_with_flags(v.focus(), v.area(), v.area_z(), Navigation::Leave)
102 }
103 }
104 builder.end(tag);
105 }
106
107 fn focus(&self) -> FocusFlag {
108 self.focus.clone()
109 }
110
111 fn area(&self) -> Rect {
112 Rect::default()
113 }
114}
115
116impl<const N: usize, Selection> RelocatableState for CalendarState<N, Selection> {
117 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
118 for w in &mut self.months {
119 w.relocate(shift, clip);
120 }
121 }
122}
123
124impl<const N: usize, Selection> CalendarState<N, Selection> {
125 pub fn new() -> Self
126 where
127 Selection: Default,
128 {
129 Self::default()
130 }
131
132 pub fn set_step(&mut self, step: usize) {
144 self.step = step;
145 }
146
147 pub fn step(&self) -> usize {
159 self.step
160 }
161
162 pub fn set_today_policy(&mut self, home: TodayPolicy) {
164 if let TodayPolicy::Index(idx) = home {
165 assert!(idx < self.months.len());
166 }
167 self.home = home;
168 }
169
170 pub fn today_policy(&mut self) -> TodayPolicy {
172 self.home
173 }
174
175 pub fn set_primary_idx(&mut self, primary: usize) {
184 assert!(primary < self.months.len());
185 self.primary_idx = primary;
186 }
187
188 pub fn primary_idx(&self) -> usize {
197 self.primary_idx
198 }
199
200 pub fn set_start_date(&mut self, mut date: NaiveDate) {
203 for month in &mut self.months {
204 month.set_start_date(date);
205 date = date + Months::new(1);
206 }
207 }
208
209 pub fn start_date(&self) -> NaiveDate {
211 self.months[0].start_date()
212 }
213
214 pub fn end_date(&self) -> NaiveDate {
216 self.months[self.months.len() - 1].end_date()
217 }
218
219 pub fn scroll_forward(&mut self, n: usize) -> CalOutcome {
222 if n == 0 {
223 return CalOutcome::Continue;
224 }
225
226 let mut start = self.months[0].start_date() + Months::new(n as u32);
228
229 for i in 0..self.months.len() {
230 self.months[i].set_start_date(start);
231 start = start + Months::new(1);
232 }
233
234 CalOutcome::Changed
235 }
236
237 pub fn scroll_back(&mut self, n: usize) -> CalOutcome {
240 if n == 0 {
241 return CalOutcome::Continue;
242 }
243
244 let mut start = self.months[0].start_date() - Months::new(n as u32);
246
247 for i in 0..self.months.len() {
248 self.months[i].set_start_date(start);
249 start = start + Months::new(1);
250 }
251
252 CalOutcome::Changed
253 }
254
255 pub fn scroll_to(&mut self, date: NaiveDate) -> CalOutcome {
258 let mut start = date - Months::new(self.primary_idx() as u32);
260
261 for i in 0..self.months.len() {
262 self.months[i].set_start_date(start);
263 start = start + Months::new(1);
264 }
265
266 CalOutcome::Changed
267 }
268}
269
270impl<const N: usize, Selection> CalendarState<N, Selection>
271where
272 Selection: CalendarSelection,
273{
274 pub fn is_selected(&self, date: NaiveDate) -> bool {
276 self.selection.is_selected(date)
277 }
278
279 pub fn lead_selection(&self) -> Option<NaiveDate> {
281 self.selection.lead_selection()
282 }
283
284 pub(super) fn focus_lead(&mut self) -> CalOutcome {
285 let Some(lead) = self.selection.lead_selection() else {
286 return CalOutcome::Continue;
287 };
288
289 let mut r = CalOutcome::Continue;
290
291 if self.is_focused() {
292 for (i, month) in self.months.iter().enumerate() {
293 if lead >= month.start_date() && lead <= month.end_date() {
294 if self.primary_idx != i {
295 r = CalOutcome::Changed;
296 }
297 self.primary_idx = i;
298 month.focus.set(true);
299 } else {
300 month.focus.set(false);
301 }
302 }
303 }
304
305 r
306 }
307
308 pub(super) fn focus_n(&mut self, n: usize) -> CalOutcome {
309 let mut r = CalOutcome::Continue;
310
311 if self.is_focused() {
312 for (i, month) in self.months.iter().enumerate() {
313 if i == n {
314 if self.primary_idx != i {
315 r = CalOutcome::Changed;
316 }
317 self.primary_idx = i;
318 month.focus.set(true);
319 } else {
320 month.focus.set(false);
321 }
322 }
323 }
324
325 r
326 }
327}
328
329impl<const N: usize> CalendarState<N, NoSelection> {
330 pub fn prev_month(&mut self, n: usize) -> CalOutcome {
333 let base_start = self.start_date();
334 let date = self.months[self.primary_idx].start_date();
335
336 let prev = date - Months::new(n as u32);
337
338 let mut r = CalOutcome::Continue;
339 if prev >= base_start {
340 self.focus_n(self.primary_idx - 1);
341 r = CalOutcome::Changed;
342 } else if self.step() > 0 {
343 r = self.scroll_back(self.step());
344 }
345
346 r
347 }
348
349 pub fn next_month(&mut self, n: usize) -> CalOutcome {
352 let base_end = self.end_date();
353 let date = self.months[self.primary_idx].start_date();
354
355 let next = date + Months::new(n as u32);
356
357 let mut r = CalOutcome::Continue;
358 if next <= base_end {
359 self.focus_n(self.primary_idx + 1);
360 r = CalOutcome::Changed;
361 } else if self.step() > 0 {
362 r = self.scroll_forward(self.step());
363 }
364 r
365 }
366
367 pub fn move_to_today(&mut self) -> CalOutcome {
373 self.move_to(Local::now().date_naive())
374 }
375
376 pub fn move_to(&mut self, date: NaiveDate) -> CalOutcome {
377 let r = CalOutcome::Changed;
378
379 match self.home {
380 TodayPolicy::Index(primary) => {
381 self.primary_idx = primary;
382 self.set_start_date(date - Months::new(primary as u32));
383 self.focus_n(self.primary_idx);
384 }
385 TodayPolicy::Year => {
386 let month = date.month0();
387 self.primary_idx = month as usize;
388 self.set_start_date(date - Months::new(month));
389 self.focus_n(self.primary_idx);
390 }
391 }
392
393 r
394 }
395}
396
397impl<const N: usize> CalendarState<N, SingleSelection> {
398 pub fn clear_selection(&mut self) {
400 self.selection.borrow_mut().clear();
401 }
402
403 pub fn select(&mut self, date: NaiveDate) -> bool {
406 self.selection.borrow_mut().select(date)
407 }
408
409 pub fn selected(&self) -> Option<NaiveDate> {
411 self.selection.borrow().selected()
412 }
413
414 pub fn move_to_today(&mut self) -> CalOutcome {
419 self.move_to(Local::now().date_naive())
420 }
421
422 pub fn move_to(&mut self, date: NaiveDate) -> CalOutcome {
428 let mut r = CalOutcome::Changed;
429
430 if self.selection.borrow_mut().select(date) {
431 r = CalOutcome::Selected;
432 }
433 match self.home {
434 TodayPolicy::Index(primary) => {
435 self.primary_idx = primary;
436 self.set_start_date(date - Months::new(primary as u32));
437 self.focus_lead();
438 }
439 TodayPolicy::Year => {
440 let month = date.month0();
441 self.primary_idx = month as usize;
442 self.set_start_date(date - Months::new(month));
443 self.focus_lead();
444 }
445 }
446
447 r
448 }
449
450 pub fn prev_day(&mut self, n: usize) -> CalOutcome {
453 self.prev(Months::new(0), Days::new(n as u64))
454 }
455
456 pub fn next_day(&mut self, n: usize) -> CalOutcome {
459 self.next(Months::new(0), Days::new(n as u64))
460 }
461
462 pub fn prev_month(&mut self, n: usize) -> CalOutcome {
467 self.prev(Months::new(n as u32), Days::new(0))
468 }
469
470 pub fn next_month(&mut self, n: usize) -> CalOutcome {
475 self.next(Months::new(n as u32), Days::new(0))
476 }
477
478 fn prev(&mut self, months: Months, days: Days) -> CalOutcome {
480 let base_start = self.start_date();
481 let base_end = self.end_date();
482
483 let new_date = if let Some(date) = self.selection.lead_selection() {
484 if date >= base_start && date <= base_end {
485 date - months - days
486 } else if date < base_start {
487 self.start_date()
488 } else {
489 self.end_date()
490 }
491 } else {
492 self.end_date()
493 };
494
495 let mut r = CalOutcome::Continue;
496
497 if new_date >= base_start && new_date <= base_end {
498 if self.selection.borrow_mut().select(new_date) {
499 r = CalOutcome::Selected;
500 }
501 } else if self.step > 0 {
502 r = r.max(self.scroll_back(self.step));
503 if self.selection.borrow_mut().select(new_date) {
504 r = r.max(CalOutcome::Selected);
505 }
506 }
507
508 if r.is_consumed() {
509 self.focus_lead();
510 }
511
512 r
513 }
514
515 fn next(&mut self, months: Months, days: Days) -> CalOutcome {
517 let base_start = self.start_date();
518 let base_end = self.end_date();
519
520 let new_date = if let Some(date) = self.selection.lead_selection() {
521 if date >= base_start && date <= base_end {
522 date + months + days
523 } else if date < base_start {
524 self.start_date()
525 } else {
526 self.end_date()
527 }
528 } else {
529 self.start_date()
530 };
531
532 let mut r = CalOutcome::Continue;
533
534 if new_date >= base_start && new_date <= base_end {
535 if self.selection.borrow_mut().select(new_date) {
536 r = CalOutcome::Selected;
537 }
538 } else if self.step > 0 {
539 r = self.scroll_forward(self.step);
540 if self.selection.borrow_mut().select(new_date) {
541 r = r.max(CalOutcome::Selected);
542 }
543 }
544
545 if r.is_consumed() {
546 self.focus_lead();
547 }
548
549 r
550 }
551}
552
553impl<const N: usize> CalendarState<N, RangeSelection> {
554 pub fn clear_selection(&mut self) {
556 self.selection.borrow_mut().clear();
557 }
558
559 pub fn select_month(&mut self, date: NaiveDate, extend: bool) -> bool {
563 self.selection.borrow_mut().select_month(date, extend)
564 }
565
566 pub fn select_week(&mut self, date: NaiveDate, extend: bool) -> bool {
570 self.selection.borrow_mut().select_week(date, extend)
571 }
572
573 pub fn select_day(&mut self, date: NaiveDate, extend: bool) -> bool {
576 self.selection.borrow_mut().select_day(date, extend)
577 }
578
579 pub fn select(&mut self, selection: (NaiveDate, NaiveDate)) -> bool {
581 self.selection.borrow_mut().select(selection)
582 }
583
584 pub fn selected(&self) -> Option<(NaiveDate, NaiveDate)> {
586 self.selection.borrow().selected()
587 }
588
589 pub fn selected_range(&self) -> Option<RangeInclusive<NaiveDate>> {
591 self.selection.borrow().selected_range()
592 }
593
594 pub fn move_to_today(&mut self) -> CalOutcome {
599 self.move_to(Local::now().date_naive())
600 }
601
602 pub fn move_to(&mut self, date: NaiveDate) -> CalOutcome {
607 let mut r = CalOutcome::Changed;
608
609 if self.selection.borrow_mut().select_day(date, false) {
610 r = CalOutcome::Selected;
611 }
612 match self.home {
613 TodayPolicy::Index(primary) => {
614 self.primary_idx = primary;
615 self.set_start_date(date - Months::new(primary as u32));
616 self.focus_lead();
617 }
618 TodayPolicy::Year => {
619 let month = date.month0();
620 self.primary_idx = month as usize;
621 self.set_start_date(date - Months::new(month));
622 self.focus_lead();
623 }
624 }
625
626 r
627 }
628
629 pub fn move_to_prev(&mut self, months: Months, days: Days) -> CalOutcome {
632 let base_start = self.start_date();
633 let base_end = self.end_date();
634
635 let new_date = if let Some(date) = self.selection.lead_selection() {
636 if date >= base_start && date <= base_end {
637 date - months - days
638 } else if date < base_start {
639 self.start_date()
640 } else {
641 self.end_date()
642 }
643 } else {
644 self.end_date()
645 };
646
647 let mut r = CalOutcome::Continue;
648
649 if new_date >= base_start && new_date <= base_end {
650 if self.selection.borrow_mut().select_day(new_date, false) {
651 r = CalOutcome::Selected;
652 }
653 } else if self.step > 0 {
654 r = r.max(self.scroll_back(self.step));
655 if self.selection.borrow_mut().select_day(new_date, false) {
656 r = r.max(CalOutcome::Selected);
657 }
658 }
659
660 if r.is_consumed() {
661 self.focus_lead();
662 }
663
664 r
665 }
666
667 pub fn move_to_next(&mut self, months: Months, days: Days) -> CalOutcome {
670 let base_start = self.start_date();
671 let base_end = self.end_date();
672
673 let new_date = if let Some(date) = self.selection.lead_selection() {
674 if date >= base_start && date <= base_end {
675 date + months + days
676 } else if date < base_start {
677 self.start_date()
678 } else {
679 self.end_date()
680 }
681 } else {
682 self.start_date()
683 };
684
685 let mut r = CalOutcome::Continue;
686
687 if new_date >= base_start && new_date <= base_end {
688 if self.selection.borrow_mut().select_day(new_date, false) {
689 r = CalOutcome::Selected;
690 }
691 } else if self.step > 0 {
692 r = self.scroll_forward(self.step);
693 if self.selection.borrow_mut().select_day(new_date, false) {
694 r = r.max(CalOutcome::Selected);
695 }
696 }
697
698 if r.is_consumed() {
699 self.focus_lead();
700 }
701
702 r
703 }
704
705 pub fn prev_day(&mut self, n: usize, extend: bool) -> CalOutcome {
709 let base_start = self.start_date();
710 let base_end = self.end_date();
711
712 let mut r = CalOutcome::Continue;
713
714 if let Some(date) = self.selection.lead_selection() {
715 let new_date = if date >= base_start && date <= base_end || self.step != 0 {
716 date - Days::new(n as u64)
717 } else if date < base_start {
718 self.start_date()
719 } else {
720 self.end_date()
721 };
722
723 if new_date >= base_start && new_date <= base_end {
724 if self.selection.borrow_mut().select_day(new_date, extend) {
725 r = CalOutcome::Selected;
726 }
727 } else if self.step > 0 {
728 r = self.scroll_back(self.step);
729 if self.selection.borrow_mut().select_day(new_date, extend) {
730 r = CalOutcome::Selected;
731 }
732 }
733 } else {
734 let new_date = self.end_date();
735 if self.selection.borrow_mut().select_day(new_date, extend) {
736 r = CalOutcome::Selected;
737 }
738 }
739
740 if r.is_consumed() {
741 self.focus_lead();
742 }
743
744 r
745 }
746
747 pub fn next_day(&mut self, n: usize, extend: bool) -> CalOutcome {
751 let base_start = self.start_date();
752 let base_end = self.end_date();
753
754 let new_date = if let Some(date) = self.selection.lead_selection() {
755 if date >= base_start && date <= base_end || self.step > 0 {
756 date + Days::new(n as u64)
757 } else if date < base_start {
758 self.start_date()
759 } else {
760 self.end_date()
761 }
762 } else {
763 self.start_date()
764 };
765
766 let mut r = CalOutcome::Continue;
767
768 if new_date >= base_start && new_date <= base_end {
769 if self.selection.borrow_mut().select_day(new_date, extend) {
770 r = CalOutcome::Selected;
771 }
772 } else if self.step > 0 {
773 r = self.scroll_forward(self.step);
774 if self.selection.borrow_mut().select_day(new_date, extend) {
775 r = CalOutcome::Selected;
776 }
777 }
778
779 if r.is_consumed() {
780 self.focus_lead();
781 }
782
783 r
784 }
785
786 pub fn prev_week(&mut self, n: usize, extend: bool) -> CalOutcome {
790 let base_start = self.start_date();
791 let base_end = self.end_date();
792
793 let mut r = CalOutcome::Continue;
794
795 if let Some(date) = self.selection.lead_selection() {
796 let new_date = if date >= base_start && date <= base_end || self.step != 0 {
797 date - Days::new(7 * n as u64)
798 } else if date < base_start {
799 self.start_date()
800 } else {
801 self.end_date()
802 };
803
804 let new_date_end = new_date.week(Weekday::Mon).last_day();
805
806 if new_date_end >= base_start && new_date_end <= base_end {
807 if self.selection.borrow_mut().select_week(new_date, extend) {
808 r = CalOutcome::Selected;
809 }
810 } else if self.step > 0 {
811 r = self.scroll_back(self.step);
812 if self.selection.borrow_mut().select_week(new_date, extend) {
813 r = CalOutcome::Selected;
814 }
815 }
816 } else {
817 let new_date = self.end_date();
818 if self.selection.borrow_mut().select_week(new_date, extend) {
819 r = CalOutcome::Selected;
820 }
821 }
822
823 if r.is_consumed() {
824 self.focus_lead();
825 }
826
827 r
828 }
829
830 pub fn next_week(&mut self, n: usize, extend: bool) -> CalOutcome {
834 let base_start = self.start_date();
835 let base_end = self.end_date();
836
837 let new_date = if let Some(date) = self.selection.lead_selection() {
838 let date_end = date.week(Weekday::Mon).last_day();
839 if date_end >= base_start && date_end <= base_end || self.step > 0 {
840 date + Days::new(7 * n as u64)
841 } else if date_end < base_start {
842 self.start_date()
843 } else {
844 self.end_date()
845 }
846 } else {
847 self.start_date()
848 };
849
850 let mut r = CalOutcome::Continue;
851
852 if new_date >= base_start && new_date <= base_end {
853 if self.selection.borrow_mut().select_week(new_date, extend) {
854 r = CalOutcome::Selected;
855 }
856 } else if self.step > 0 {
857 r = self.scroll_forward(self.step);
858 if self.selection.borrow_mut().select_week(new_date, extend) {
859 r = CalOutcome::Selected;
860 }
861 }
862
863 if r.is_consumed() {
864 self.focus_lead();
865 }
866
867 r
868 }
869
870 pub fn prev_month(&mut self, n: usize, extend: bool) -> CalOutcome {
875 let base_start = self.start_date();
876 let base_end = self.end_date();
877
878 let mut r = CalOutcome::Continue;
879
880 if let Some(date) = self.selection.lead_selection() {
881 let new_date = if date >= base_start && date <= base_end || self.step != 0 {
882 date - Months::new(n as u32)
883 } else if date < base_start {
884 self.start_date()
885 } else {
886 self.end_date()
887 };
888
889 if new_date >= base_start && new_date <= base_end {
890 if self.selection.borrow_mut().select_month(new_date, extend) {
891 r = CalOutcome::Selected;
892 }
893 } else if self.step > 0 {
894 r = self.scroll_back(self.step);
895 if self.selection.borrow_mut().select_month(new_date, extend) {
896 r = CalOutcome::Selected;
897 }
898 }
899 } else {
900 let new_date = self.end_date();
901 if self.selection.borrow_mut().select_month(new_date, extend) {
902 r = CalOutcome::Selected;
903 }
904 }
905
906 if r.is_consumed() {
907 self.focus_lead();
908 }
909
910 r
911 }
912
913 pub fn next_month(&mut self, n: usize, extend: bool) -> CalOutcome {
918 let base_start = self.start_date();
919 let base_end = self.end_date();
920
921 let new_date = if let Some(date) = self.selection.lead_selection() {
922 if date >= base_start && date <= base_end || self.step > 0 {
923 date + Months::new(n as u32)
924 } else if date < base_start {
925 self.start_date()
926 } else {
927 self.end_date()
928 }
929 } else {
930 self.start_date()
931 };
932
933 let mut r = CalOutcome::Continue;
934
935 if new_date >= base_start && new_date <= base_end {
936 if self.selection.borrow_mut().select_month(new_date, extend) {
937 r = CalOutcome::Selected;
938 }
939 } else if self.step > 0 {
940 r = self.scroll_forward(self.step);
941 if self.selection.borrow_mut().select_month(new_date, extend) {
942 r = CalOutcome::Selected;
943 }
944 }
945
946 if r.is_consumed() {
947 self.focus_lead();
948 }
949
950 r
951 }
952}