1use crate::_private::NonExhaustive;
2use crate::calendar::event::CalOutcome;
3use crate::calendar::selection::{NoSelection, RangeSelection, SingleSelection};
4use crate::calendar::{CalendarSelection, MonthState};
5use crate::text::HasScreenCursor;
6use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday};
7use rat_event::ConsumedEvent;
8use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
9use rat_reloc::RelocatableState;
10use ratatui::layout::Rect;
11use std::array;
12use std::cell::RefCell;
13use std::ops::RangeInclusive;
14use std::rc::Rc;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum TodayPolicy {
19 Index(usize),
22 Year,
25}
26
27impl Default for TodayPolicy {
28 fn default() -> Self {
29 Self::Index(0)
30 }
31}
32
33#[derive(Debug, Clone)]
39pub struct CalendarState<const N: usize, Selection> {
40 pub area: Rect,
43 pub inner: Rect,
46 #[deprecated(since = "2.3.0", note = "use inner instead")]
49 pub widget_area: Rect,
50 step: usize,
62
63 home: TodayPolicy,
65
66 primary_idx: usize,
71
72 pub months: [MonthState<Selection>; N],
74
75 pub selection: Rc<RefCell<Selection>>,
77
78 pub focus: FocusFlag,
80
81 pub non_exhaustive: NonExhaustive,
82}
83
84impl<const N: usize, Selection> Default for CalendarState<N, Selection>
85where
86 Selection: Default,
87{
88 #[allow(deprecated)]
89 fn default() -> Self {
90 let selection = Rc::new(RefCell::new(Selection::default()));
91 let focus = FocusFlag::new();
92
93 Self {
94 area: Default::default(),
95 inner: Default::default(),
96 widget_area: Default::default(),
97 step: 1,
98 months: array::from_fn(|_| {
99 let mut state = MonthState::new();
100 state.selection = selection.clone();
101 state.container = Some(focus.clone());
102 state
103 }),
104 selection,
105 primary_idx: Default::default(),
106 focus,
107 home: Default::default(),
108 non_exhaustive: NonExhaustive,
109 }
110 }
111}
112
113impl<const N: usize, Selection> HasFocus for CalendarState<N, Selection> {
114 fn build(&self, builder: &mut FocusBuilder) {
115 let tag = builder.start(self);
116 for (i, v) in self.months.iter().enumerate() {
117 if i == self.primary_idx {
118 builder.widget(v); } else {
120 builder.widget_with_flags(v.focus(), v.area(), v.area_z(), Navigation::Leave)
121 }
122 }
123 builder.end(tag);
124 }
125
126 fn focus(&self) -> FocusFlag {
127 self.focus.clone()
128 }
129
130 fn area(&self) -> Rect {
131 Rect::default()
132 }
133}
134
135impl<const N: usize, Selection> HasScreenCursor for CalendarState<N, Selection> {
136 fn screen_cursor(&self) -> Option<(u16, u16)> {
137 None
138 }
139}
140
141impl<const N: usize, Selection> RelocatableState for CalendarState<N, Selection> {
142 #[allow(deprecated)]
143 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
144 self.area.relocate(shift, clip);
145 self.inner.relocate(shift, clip);
146 self.widget_area.relocate(shift, clip);
147
148 for w in &mut self.months {
149 w.relocate(shift, clip);
150 }
151 }
152}
153
154impl<const N: usize, Selection> CalendarState<N, Selection> {
155 pub fn new() -> Self
156 where
157 Selection: Default,
158 {
159 Self::default()
160 }
161
162 pub fn named(name: &str) -> Self
163 where
164 Selection: Default,
165 {
166 let mut z = Self::default();
167 z.focus = z.focus.with_name(name);
168 z
169 }
170
171 pub fn set_step(&mut self, step: usize) {
183 self.step = step;
184 }
185
186 pub fn step(&self) -> usize {
198 self.step
199 }
200
201 pub fn set_today_policy(&mut self, home: TodayPolicy) {
203 if let TodayPolicy::Index(idx) = home {
204 assert!(idx < self.months.len());
205 }
206 self.home = home;
207 }
208
209 pub fn today_policy(&mut self) -> TodayPolicy {
211 self.home
212 }
213
214 pub fn set_primary_idx(&mut self, primary: usize) {
223 assert!(primary < self.months.len());
224 self.primary_idx = primary;
225 }
226
227 pub fn primary_idx(&self) -> usize {
236 self.primary_idx
237 }
238
239 pub fn set_start_date(&mut self, mut date: NaiveDate) {
242 for month in &mut self.months {
243 month.set_start_date(date);
244 date = date + Months::new(1);
245 }
246 }
247
248 pub fn start_date(&self) -> NaiveDate {
250 self.months[0].start_date()
251 }
252
253 pub fn end_date(&self) -> NaiveDate {
255 self.months[self.months.len() - 1].end_date()
256 }
257
258 pub fn scroll_forward(&mut self, n: usize) -> CalOutcome {
261 if n == 0 {
262 return CalOutcome::Continue;
263 }
264
265 let mut start = self.months[0].start_date() + Months::new(n as u32);
267
268 for i in 0..self.months.len() {
269 self.months[i].set_start_date(start);
270 start = start + Months::new(1);
271 }
272
273 CalOutcome::Changed
274 }
275
276 pub fn scroll_back(&mut self, n: usize) -> CalOutcome {
279 if n == 0 {
280 return CalOutcome::Continue;
281 }
282
283 let mut start = self.months[0].start_date() - Months::new(n as u32);
285
286 for i in 0..self.months.len() {
287 self.months[i].set_start_date(start);
288 start = start + Months::new(1);
289 }
290
291 CalOutcome::Changed
292 }
293
294 pub fn scroll_to(&mut self, date: NaiveDate) -> CalOutcome {
297 let mut start = date - Months::new(self.primary_idx() as u32);
299
300 for i in 0..self.months.len() {
301 self.months[i].set_start_date(start);
302 start = start + Months::new(1);
303 }
304
305 CalOutcome::Changed
306 }
307
308 pub fn screen_cursor(&self) -> Option<(u16, u16)> {
310 None
311 }
312}
313
314impl<const N: usize, Selection> CalendarState<N, Selection>
315where
316 Selection: CalendarSelection,
317{
318 pub fn is_selected(&self, date: NaiveDate) -> bool {
320 self.selection.is_selected(date)
321 }
322
323 pub fn lead_selection(&self) -> Option<NaiveDate> {
325 self.selection.lead_selection()
326 }
327
328 pub(super) fn focus_lead(&mut self) -> CalOutcome {
329 let Some(lead) = self.selection.lead_selection() else {
330 return CalOutcome::Continue;
331 };
332
333 let mut r = CalOutcome::Continue;
334
335 if self.is_focused() {
336 for (i, month) in self.months.iter().enumerate() {
337 if lead >= month.start_date() && lead <= month.end_date() {
338 if self.primary_idx != i {
339 r = CalOutcome::Changed;
340 }
341 self.primary_idx = i;
342 month.focus.set(true);
343 } else {
344 month.focus.set(false);
345 }
346 }
347 }
348
349 r
350 }
351
352 pub(super) fn focus_n(&mut self, n: usize) -> CalOutcome {
353 let mut r = CalOutcome::Continue;
354
355 if self.is_focused() {
356 for (i, month) in self.months.iter().enumerate() {
357 if i == n {
358 if self.primary_idx != i {
359 r = CalOutcome::Changed;
360 }
361 self.primary_idx = i;
362 month.focus.set(true);
363 } else {
364 month.focus.set(false);
365 }
366 }
367 }
368
369 r
370 }
371}
372
373impl<const N: usize> CalendarState<N, NoSelection> {
374 pub fn prev_month(&mut self, n: usize) -> CalOutcome {
377 let base_start = self.start_date();
378 let date = self.months[self.primary_idx].start_date();
379
380 let prev = date - Months::new(n as u32);
381
382 let mut r = CalOutcome::Continue;
383 if prev >= base_start {
384 self.focus_n(self.primary_idx - 1);
385 r = CalOutcome::Changed;
386 } else if self.step() > 0 {
387 r = self.scroll_back(self.step());
388 }
389
390 r
391 }
392
393 pub fn next_month(&mut self, n: usize) -> CalOutcome {
396 let base_end = self.end_date();
397 let date = self.months[self.primary_idx].start_date();
398
399 let next = date + Months::new(n as u32);
400
401 let mut r = CalOutcome::Continue;
402 if next <= base_end {
403 self.focus_n(self.primary_idx + 1);
404 r = CalOutcome::Changed;
405 } else if self.step() > 0 {
406 r = self.scroll_forward(self.step());
407 }
408 r
409 }
410
411 pub fn move_to_today(&mut self) -> CalOutcome {
417 self.move_to(Local::now().date_naive())
418 }
419
420 pub fn move_to(&mut self, date: NaiveDate) -> CalOutcome {
421 let r = CalOutcome::Changed;
422
423 match self.home {
424 TodayPolicy::Index(primary) => {
425 self.primary_idx = primary;
426 self.set_start_date(date - Months::new(primary as u32));
427 self.focus_n(self.primary_idx);
428 }
429 TodayPolicy::Year => {
430 let month = date.month0();
431 self.primary_idx = month as usize;
432 self.set_start_date(date - Months::new(month));
433 self.focus_n(self.primary_idx);
434 }
435 }
436
437 r
438 }
439}
440
441impl<const N: usize> CalendarState<N, SingleSelection> {
442 pub fn clear_selection(&mut self) {
444 self.selection.borrow_mut().clear();
445 }
446
447 pub fn select(&mut self, date: NaiveDate) -> bool {
450 self.selection.borrow_mut().select(date)
451 }
452
453 pub fn selected(&self) -> Option<NaiveDate> {
455 self.selection.borrow().selected()
456 }
457
458 pub fn move_to_today(&mut self) -> CalOutcome {
463 self.move_to(Local::now().date_naive())
464 }
465
466 pub fn move_to(&mut self, date: NaiveDate) -> CalOutcome {
472 let mut r = CalOutcome::Changed;
473
474 if self.selection.borrow_mut().select(date) {
475 r = CalOutcome::Selected;
476 }
477 match self.home {
478 TodayPolicy::Index(primary) => {
479 self.primary_idx = primary;
480 self.set_start_date(date - Months::new(primary as u32));
481 self.focus_lead();
482 }
483 TodayPolicy::Year => {
484 let month = date.month0();
485 self.primary_idx = month as usize;
486 self.set_start_date(date - Months::new(month));
487 self.focus_lead();
488 }
489 }
490
491 r
492 }
493
494 pub fn prev_day(&mut self, n: usize) -> CalOutcome {
497 self.prev(Months::new(0), Days::new(n as u64))
498 }
499
500 pub fn next_day(&mut self, n: usize) -> CalOutcome {
503 self.next(Months::new(0), Days::new(n as u64))
504 }
505
506 pub fn prev_month(&mut self, n: usize) -> CalOutcome {
511 self.prev(Months::new(n as u32), Days::new(0))
512 }
513
514 pub fn next_month(&mut self, n: usize) -> CalOutcome {
519 self.next(Months::new(n as u32), Days::new(0))
520 }
521
522 fn prev(&mut self, months: Months, days: Days) -> CalOutcome {
524 let base_start = self.start_date();
525 let base_end = self.end_date();
526
527 let new_date = if let Some(date) = self.selection.lead_selection() {
528 if date >= base_start && date <= base_end {
529 date - months - days
530 } else if date < base_start {
531 self.start_date()
532 } else {
533 self.end_date()
534 }
535 } else {
536 self.end_date()
537 };
538
539 let mut r = CalOutcome::Continue;
540
541 if new_date >= base_start && new_date <= base_end {
542 if self.selection.borrow_mut().select(new_date) {
543 r = CalOutcome::Selected;
544 }
545 } else if self.step > 0 {
546 r = r.max(self.scroll_back(self.step));
547 if self.selection.borrow_mut().select(new_date) {
548 r = r.max(CalOutcome::Selected);
549 }
550 }
551
552 if r.is_consumed() {
553 self.focus_lead();
554 }
555
556 r
557 }
558
559 fn next(&mut self, months: Months, days: Days) -> CalOutcome {
561 let base_start = self.start_date();
562 let base_end = self.end_date();
563
564 let new_date = if let Some(date) = self.selection.lead_selection() {
565 if date >= base_start && date <= base_end {
566 date + months + days
567 } else if date < base_start {
568 self.start_date()
569 } else {
570 self.end_date()
571 }
572 } else {
573 self.start_date()
574 };
575
576 let mut r = CalOutcome::Continue;
577
578 if new_date >= base_start && new_date <= base_end {
579 if self.selection.borrow_mut().select(new_date) {
580 r = CalOutcome::Selected;
581 }
582 } else if self.step > 0 {
583 r = self.scroll_forward(self.step);
584 if self.selection.borrow_mut().select(new_date) {
585 r = r.max(CalOutcome::Selected);
586 }
587 }
588
589 if r.is_consumed() {
590 self.focus_lead();
591 }
592
593 r
594 }
595}
596
597impl<const N: usize> CalendarState<N, RangeSelection> {
598 pub fn clear_selection(&mut self) {
600 self.selection.borrow_mut().clear();
601 }
602
603 pub fn select_month(&mut self, date: NaiveDate, extend: bool) -> bool {
607 self.selection.borrow_mut().select_month(date, extend)
608 }
609
610 pub fn select_week(&mut self, date: NaiveDate, extend: bool) -> bool {
614 self.selection.borrow_mut().select_week(date, extend)
615 }
616
617 pub fn select_day(&mut self, date: NaiveDate, extend: bool) -> bool {
620 self.selection.borrow_mut().select_day(date, extend)
621 }
622
623 pub fn select(&mut self, selection: (NaiveDate, NaiveDate)) -> bool {
625 self.selection.borrow_mut().select(selection)
626 }
627
628 pub fn selected(&self) -> Option<(NaiveDate, NaiveDate)> {
630 self.selection.borrow().selected()
631 }
632
633 pub fn selected_range(&self) -> Option<RangeInclusive<NaiveDate>> {
635 self.selection.borrow().selected_range()
636 }
637
638 pub fn move_to_today(&mut self) -> CalOutcome {
643 self.move_to(Local::now().date_naive())
644 }
645
646 pub fn move_to(&mut self, date: NaiveDate) -> CalOutcome {
651 let mut r = CalOutcome::Changed;
652
653 if self.selection.borrow_mut().select_day(date, false) {
654 r = CalOutcome::Selected;
655 }
656 match self.home {
657 TodayPolicy::Index(primary) => {
658 self.primary_idx = primary;
659 self.set_start_date(date - Months::new(primary as u32));
660 self.focus_lead();
661 }
662 TodayPolicy::Year => {
663 let month = date.month0();
664 self.primary_idx = month as usize;
665 self.set_start_date(date - Months::new(month));
666 self.focus_lead();
667 }
668 }
669
670 r
671 }
672
673 pub fn move_to_prev(&mut self, months: Months, days: Days) -> CalOutcome {
676 let base_start = self.start_date();
677 let base_end = self.end_date();
678
679 let new_date = if let Some(date) = self.selection.lead_selection() {
680 if date >= base_start && date <= base_end {
681 date - months - days
682 } else if date < base_start {
683 self.start_date()
684 } else {
685 self.end_date()
686 }
687 } else {
688 self.end_date()
689 };
690
691 let mut r = CalOutcome::Continue;
692
693 if new_date >= base_start && new_date <= base_end {
694 if self.selection.borrow_mut().select_day(new_date, false) {
695 r = CalOutcome::Selected;
696 }
697 } else if self.step > 0 {
698 r = r.max(self.scroll_back(self.step));
699 if self.selection.borrow_mut().select_day(new_date, false) {
700 r = r.max(CalOutcome::Selected);
701 }
702 }
703
704 if r.is_consumed() {
705 self.focus_lead();
706 }
707
708 r
709 }
710
711 pub fn move_to_next(&mut self, months: Months, days: Days) -> CalOutcome {
714 let base_start = self.start_date();
715 let base_end = self.end_date();
716
717 let new_date = if let Some(date) = self.selection.lead_selection() {
718 if date >= base_start && date <= base_end {
719 date + months + days
720 } else if date < base_start {
721 self.start_date()
722 } else {
723 self.end_date()
724 }
725 } else {
726 self.start_date()
727 };
728
729 let mut r = CalOutcome::Continue;
730
731 if new_date >= base_start && new_date <= base_end {
732 if self.selection.borrow_mut().select_day(new_date, false) {
733 r = CalOutcome::Selected;
734 }
735 } else if self.step > 0 {
736 r = self.scroll_forward(self.step);
737 if self.selection.borrow_mut().select_day(new_date, false) {
738 r = r.max(CalOutcome::Selected);
739 }
740 }
741
742 if r.is_consumed() {
743 self.focus_lead();
744 }
745
746 r
747 }
748
749 pub fn prev_day(&mut self, n: usize, extend: bool) -> CalOutcome {
753 let base_start = self.start_date();
754 let base_end = self.end_date();
755
756 let mut r = CalOutcome::Continue;
757
758 if let Some(date) = self.selection.lead_selection() {
759 let new_date = if date >= base_start && date <= base_end || self.step != 0 {
760 date - Days::new(n as u64)
761 } else if date < base_start {
762 self.start_date()
763 } else {
764 self.end_date()
765 };
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_back(self.step);
773 if self.selection.borrow_mut().select_day(new_date, extend) {
774 r = CalOutcome::Selected;
775 }
776 }
777 } else {
778 let new_date = self.end_date();
779 if self.selection.borrow_mut().select_day(new_date, extend) {
780 r = CalOutcome::Selected;
781 }
782 }
783
784 if r.is_consumed() {
785 self.focus_lead();
786 }
787
788 r
789 }
790
791 pub fn next_day(&mut self, n: usize, extend: bool) -> CalOutcome {
795 let base_start = self.start_date();
796 let base_end = self.end_date();
797
798 let new_date = if let Some(date) = self.selection.lead_selection() {
799 if date >= base_start && date <= base_end || self.step > 0 {
800 date + Days::new(n as u64)
801 } else if date < base_start {
802 self.start_date()
803 } else {
804 self.end_date()
805 }
806 } else {
807 self.start_date()
808 };
809
810 let mut r = CalOutcome::Continue;
811
812 if new_date >= base_start && new_date <= base_end {
813 if self.selection.borrow_mut().select_day(new_date, extend) {
814 r = CalOutcome::Selected;
815 }
816 } else if self.step > 0 {
817 r = self.scroll_forward(self.step);
818 if self.selection.borrow_mut().select_day(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 prev_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 mut r = CalOutcome::Continue;
838
839 if let Some(date) = self.selection.lead_selection() {
840 let new_date = if date >= base_start && date <= base_end || self.step != 0 {
841 date - Days::new(7 * n as u64)
842 } else if date < base_start {
843 self.start_date()
844 } else {
845 self.end_date()
846 };
847
848 let new_date_end = new_date.week(Weekday::Mon).last_day();
849
850 if new_date_end >= base_start && new_date_end <= base_end {
851 if self.selection.borrow_mut().select_week(new_date, extend) {
852 r = CalOutcome::Selected;
853 }
854 } else if self.step > 0 {
855 r = self.scroll_back(self.step);
856 if self.selection.borrow_mut().select_week(new_date, extend) {
857 r = CalOutcome::Selected;
858 }
859 }
860 } else {
861 let new_date = self.end_date();
862 if self.selection.borrow_mut().select_week(new_date, extend) {
863 r = CalOutcome::Selected;
864 }
865 }
866
867 if r.is_consumed() {
868 self.focus_lead();
869 }
870
871 r
872 }
873
874 pub fn next_week(&mut self, n: usize, extend: bool) -> CalOutcome {
878 let base_start = self.start_date();
879 let base_end = self.end_date();
880
881 let new_date = if let Some(date) = self.selection.lead_selection() {
882 let date_end = date.week(Weekday::Mon).last_day();
883 if date_end >= base_start && date_end <= base_end || self.step > 0 {
884 date + Days::new(7 * n as u64)
885 } else if date_end < base_start {
886 self.start_date()
887 } else {
888 self.end_date()
889 }
890 } else {
891 self.start_date()
892 };
893
894 let mut r = CalOutcome::Continue;
895
896 if new_date >= base_start && new_date <= base_end {
897 if self.selection.borrow_mut().select_week(new_date, extend) {
898 r = CalOutcome::Selected;
899 }
900 } else if self.step > 0 {
901 r = self.scroll_forward(self.step);
902 if self.selection.borrow_mut().select_week(new_date, extend) {
903 r = CalOutcome::Selected;
904 }
905 }
906
907 if r.is_consumed() {
908 self.focus_lead();
909 }
910
911 r
912 }
913
914 pub fn prev_month(&mut self, n: usize, extend: bool) -> CalOutcome {
919 let base_start = self.start_date();
920 let base_end = self.end_date();
921
922 let mut r = CalOutcome::Continue;
923
924 if let Some(date) = self.selection.lead_selection() {
925 let new_date = if date >= base_start && date <= base_end || self.step != 0 {
926 date - Months::new(n as u32)
927 } else if date < base_start {
928 self.start_date()
929 } else {
930 self.end_date()
931 };
932
933 if new_date >= base_start && new_date <= base_end {
934 if self.selection.borrow_mut().select_month(new_date, extend) {
935 r = CalOutcome::Selected;
936 }
937 } else if self.step > 0 {
938 r = self.scroll_back(self.step);
939 if self.selection.borrow_mut().select_month(new_date, extend) {
940 r = CalOutcome::Selected;
941 }
942 }
943 } else {
944 let new_date = self.end_date();
945 if self.selection.borrow_mut().select_month(new_date, extend) {
946 r = CalOutcome::Selected;
947 }
948 }
949
950 if r.is_consumed() {
951 self.focus_lead();
952 }
953
954 r
955 }
956
957 pub fn next_month(&mut self, n: usize, extend: bool) -> CalOutcome {
962 let base_start = self.start_date();
963 let base_end = self.end_date();
964
965 let new_date = if let Some(date) = self.selection.lead_selection() {
966 if date >= base_start && date <= base_end || self.step > 0 {
967 date + Months::new(n as u32)
968 } else if date < base_start {
969 self.start_date()
970 } else {
971 self.end_date()
972 }
973 } else {
974 self.start_date()
975 };
976
977 let mut r = CalOutcome::Continue;
978
979 if new_date >= base_start && new_date <= base_end {
980 if self.selection.borrow_mut().select_month(new_date, extend) {
981 r = CalOutcome::Selected;
982 }
983 } else if self.step > 0 {
984 r = self.scroll_forward(self.step);
985 if self.selection.borrow_mut().select_month(new_date, extend) {
986 r = CalOutcome::Selected;
987 }
988 }
989
990 if r.is_consumed() {
991 self.focus_lead();
992 }
993
994 r
995 }
996}