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