use crate::calendar::event::CalOutcome;
use crate::calendar::selection::{NoSelection, RangeSelection, SingleSelection};
use crate::calendar::{CalendarSelection, MonthState};
use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday};
use rat_event::ConsumedEvent;
use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
use rat_reloc::RelocatableState;
use ratatui::layout::Rect;
use std::array;
use std::cell::RefCell;
use std::ops::RangeInclusive;
use std::rc::Rc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TodayPolicy {
Index(usize),
Year,
}
impl Default for TodayPolicy {
fn default() -> Self {
Self::Index(0)
}
}
#[derive(Debug, Clone)]
pub struct CalendarState<const N: usize, Selection> {
step: usize,
home: TodayPolicy,
primary_idx: usize,
pub months: [MonthState<Selection>; N],
pub selection: Rc<RefCell<Selection>>,
pub focus: FocusFlag,
}
impl<const N: usize, Selection> Default for CalendarState<N, Selection>
where
Selection: Default,
{
fn default() -> Self {
let selection = Rc::new(RefCell::new(Selection::default()));
let focus = FocusFlag::new();
Self {
step: 1,
months: array::from_fn(|_| {
let mut state = MonthState::new();
state.selection = selection.clone();
state.container = Some(focus.clone());
state
}),
selection,
primary_idx: Default::default(),
focus,
home: Default::default(),
}
}
}
impl<const N: usize, Selection> HasFocus for CalendarState<N, Selection> {
fn build(&self, builder: &mut FocusBuilder) {
let tag = builder.start(self);
for (i, v) in self.months.iter().enumerate() {
if i == self.primary_idx {
builder.widget(v); } else {
builder.append_flags(v.focus(), v.area(), v.area_z(), Navigation::Leave)
}
}
builder.end(tag);
}
fn focus(&self) -> FocusFlag {
self.focus.clone()
}
fn area(&self) -> Rect {
Rect::default()
}
}
impl<const N: usize, Selection> RelocatableState for CalendarState<N, Selection> {
fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
for w in &mut self.months {
w.relocate(shift, clip);
}
}
}
impl<const N: usize, Selection> CalendarState<N, Selection> {
pub fn new() -> Self
where
Selection: Default,
{
Self::default()
}
pub fn set_step(&mut self, step: usize) {
self.step = step;
}
pub fn step(&self) -> usize {
self.step
}
pub fn set_today_policy(&mut self, home: TodayPolicy) {
if let TodayPolicy::Index(idx) = home {
assert!(idx < self.months.len());
}
self.home = home;
}
pub fn today_policy(&mut self) -> TodayPolicy {
self.home
}
pub fn set_primary_idx(&mut self, primary: usize) {
assert!(primary < self.months.len());
self.primary_idx = primary;
}
pub fn primary_idx(&self) -> usize {
self.primary_idx
}
pub fn set_start_date(&mut self, mut date: NaiveDate) {
for month in &mut self.months {
month.set_start_date(date);
date = date + Months::new(1);
}
}
pub fn start_date(&self) -> NaiveDate {
self.months[0].start_date()
}
pub fn end_date(&self) -> NaiveDate {
self.months[self.months.len() - 1].end_date()
}
pub fn scroll_forward(&mut self, n: usize) -> CalOutcome {
if n == 0 {
return CalOutcome::Continue;
}
let mut start = self.months[0].start_date() + Months::new(n as u32);
for i in 0..self.months.len() {
self.months[i].set_start_date(start);
start = start + Months::new(1);
}
CalOutcome::Changed
}
pub fn scroll_back(&mut self, n: usize) -> CalOutcome {
if n == 0 {
return CalOutcome::Continue;
}
let mut start = self.months[0].start_date() - Months::new(n as u32);
for i in 0..self.months.len() {
self.months[i].set_start_date(start);
start = start + Months::new(1);
}
CalOutcome::Changed
}
pub fn scroll_to(&mut self, date: NaiveDate) -> CalOutcome {
let mut start = date - Months::new(self.primary_idx() as u32);
for i in 0..self.months.len() {
self.months[i].set_start_date(start);
start = start + Months::new(1);
}
CalOutcome::Changed
}
}
impl<const N: usize, Selection> CalendarState<N, Selection>
where
Selection: CalendarSelection,
{
pub fn clear(&mut self) {
self.selection.clear();
}
pub fn is_selected(&self, date: NaiveDate) -> bool {
self.selection.is_selected(date)
}
pub fn lead_selection(&self) -> Option<NaiveDate> {
self.selection.lead_selection()
}
pub(super) fn focus_lead(&mut self) -> CalOutcome {
let Some(lead) = self.selection.lead_selection() else {
return CalOutcome::Continue;
};
let mut r = CalOutcome::Continue;
if self.is_focused() {
for (i, month) in self.months.iter().enumerate() {
if lead >= month.start_date() && lead <= month.end_date() {
if self.primary_idx != i {
r = CalOutcome::Changed;
}
self.primary_idx = i;
month.focus.set(true);
} else {
month.focus.set(false);
}
}
}
r
}
pub(super) fn focus_n(&mut self, n: usize) -> CalOutcome {
let mut r = CalOutcome::Continue;
if self.is_focused() {
for (i, month) in self.months.iter().enumerate() {
if i == n {
if self.primary_idx != i {
r = CalOutcome::Changed;
}
self.primary_idx = i;
month.focus.set(true);
} else {
month.focus.set(false);
}
}
}
r
}
}
impl<const N: usize> CalendarState<N, NoSelection> {
pub fn prev_month(&mut self, n: usize) -> CalOutcome {
let base_start = self.start_date();
let date = self.months[self.primary_idx].start_date();
let prev = date - Months::new(n as u32);
let mut r = CalOutcome::Continue;
if prev >= base_start {
self.focus_n(self.primary_idx - 1);
r = CalOutcome::Changed;
} else if self.step() > 0 {
r = self.scroll_back(self.step());
}
r
}
pub fn next_month(&mut self, n: usize) -> CalOutcome {
let base_end = self.end_date();
let date = self.months[self.primary_idx].start_date();
let next = date + Months::new(n as u32);
let mut r = CalOutcome::Continue;
if next <= base_end {
self.focus_n(self.primary_idx + 1);
r = CalOutcome::Changed;
} else if self.step() > 0 {
r = self.scroll_forward(self.step());
}
r
}
pub fn move_to_today(&mut self) -> CalOutcome {
self.move_to(Local::now().date_naive())
}
pub fn move_to(&mut self, date: NaiveDate) -> CalOutcome {
let r = CalOutcome::Changed;
match self.home {
TodayPolicy::Index(primary) => {
self.primary_idx = primary;
self.set_start_date(date - Months::new(primary as u32));
self.focus_n(self.primary_idx);
}
TodayPolicy::Year => {
let month = date.month0();
self.primary_idx = month as usize;
self.set_start_date(date - Months::new(month));
self.focus_n(self.primary_idx);
}
}
r
}
}
impl<const N: usize> CalendarState<N, SingleSelection> {
pub fn select(&mut self, date: NaiveDate) -> bool {
self.selection.borrow_mut().select(date)
}
pub fn selected(&self) -> Option<NaiveDate> {
self.selection.borrow().selected()
}
pub fn move_to_today(&mut self) -> CalOutcome {
self.move_to(Local::now().date_naive())
}
pub fn move_to(&mut self, date: NaiveDate) -> CalOutcome {
let mut r = CalOutcome::Changed;
if self.selection.borrow_mut().select(date) {
r = CalOutcome::Selected;
}
match self.home {
TodayPolicy::Index(primary) => {
self.primary_idx = primary;
self.set_start_date(date - Months::new(primary as u32));
self.focus_lead();
}
TodayPolicy::Year => {
let month = date.month0();
self.primary_idx = month as usize;
self.set_start_date(date - Months::new(month));
self.focus_lead();
}
}
r
}
pub fn prev_day(&mut self, n: usize) -> CalOutcome {
self.prev(Months::new(0), Days::new(n as u64))
}
pub fn next_day(&mut self, n: usize) -> CalOutcome {
self.next(Months::new(0), Days::new(n as u64))
}
pub fn prev_month(&mut self, n: usize) -> CalOutcome {
self.prev(Months::new(n as u32), Days::new(0))
}
pub fn next_month(&mut self, n: usize) -> CalOutcome {
self.next(Months::new(n as u32), Days::new(0))
}
fn prev(&mut self, months: Months, days: Days) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let new_date = if let Some(date) = self.selection.lead_selection() {
if date >= base_start && date <= base_end {
date - months - days
} else if date < base_start {
self.start_date()
} else {
self.end_date()
}
} else {
self.end_date()
};
let mut r = CalOutcome::Continue;
if new_date >= base_start && new_date <= base_end {
if self.selection.borrow_mut().select(new_date) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = r.max(self.scroll_back(self.step));
if self.selection.borrow_mut().select(new_date) {
r = r.max(CalOutcome::Selected);
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
fn next(&mut self, months: Months, days: Days) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let new_date = if let Some(date) = self.selection.lead_selection() {
if date >= base_start && date <= base_end {
date + months + days
} else if date < base_start {
self.start_date()
} else {
self.end_date()
}
} else {
self.start_date()
};
let mut r = CalOutcome::Continue;
if new_date >= base_start && new_date <= base_end {
if self.selection.borrow_mut().select(new_date) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = self.scroll_forward(self.step);
if self.selection.borrow_mut().select(new_date) {
r = r.max(CalOutcome::Selected);
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
}
impl<const N: usize> CalendarState<N, RangeSelection> {
pub fn select_month(&mut self, date: NaiveDate, extend: bool) -> bool {
self.selection.borrow_mut().select_month(date, extend)
}
pub fn select_week(&mut self, date: NaiveDate, extend: bool) -> bool {
self.selection.borrow_mut().select_week(date, extend)
}
pub fn select_day(&mut self, date: NaiveDate, extend: bool) -> bool {
self.selection.borrow_mut().select_day(date, extend)
}
pub fn select(&mut self, selection: (NaiveDate, NaiveDate)) -> bool {
self.selection.borrow_mut().select(selection)
}
pub fn selected(&self) -> Option<(NaiveDate, NaiveDate)> {
self.selection.borrow().selected()
}
pub fn selected_range(&self) -> Option<RangeInclusive<NaiveDate>> {
self.selection.borrow().selected_range()
}
pub fn move_to_today(&mut self) -> CalOutcome {
self.move_to(Local::now().date_naive())
}
pub fn move_to(&mut self, date: NaiveDate) -> CalOutcome {
let mut r = CalOutcome::Changed;
if self.selection.borrow_mut().select_day(date, false) {
r = CalOutcome::Selected;
}
match self.home {
TodayPolicy::Index(primary) => {
self.primary_idx = primary;
self.set_start_date(date - Months::new(primary as u32));
self.focus_lead();
}
TodayPolicy::Year => {
let month = date.month0();
self.primary_idx = month as usize;
self.set_start_date(date - Months::new(month));
self.focus_lead();
}
}
r
}
pub fn move_to_prev(&mut self, months: Months, days: Days) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let new_date = if let Some(date) = self.selection.lead_selection() {
if date >= base_start && date <= base_end {
date - months - days
} else if date < base_start {
self.start_date()
} else {
self.end_date()
}
} else {
self.end_date()
};
let mut r = CalOutcome::Continue;
if new_date >= base_start && new_date <= base_end {
if self.selection.borrow_mut().select_day(new_date, false) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = r.max(self.scroll_back(self.step));
if self.selection.borrow_mut().select_day(new_date, false) {
r = r.max(CalOutcome::Selected);
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
pub fn move_to_next(&mut self, months: Months, days: Days) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let new_date = if let Some(date) = self.selection.lead_selection() {
if date >= base_start && date <= base_end {
date + months + days
} else if date < base_start {
self.start_date()
} else {
self.end_date()
}
} else {
self.start_date()
};
let mut r = CalOutcome::Continue;
if new_date >= base_start && new_date <= base_end {
if self.selection.borrow_mut().select_day(new_date, false) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = self.scroll_forward(self.step);
if self.selection.borrow_mut().select_day(new_date, false) {
r = r.max(CalOutcome::Selected);
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
pub fn prev_day(&mut self, n: usize, extend: bool) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let mut r = CalOutcome::Continue;
if let Some(date) = self.selection.lead_selection() {
let new_date = if date >= base_start && date <= base_end || self.step != 0 {
date - Days::new(n as u64)
} else if date < base_start {
self.start_date()
} else {
self.end_date()
};
if new_date >= base_start && new_date <= base_end {
if self.selection.borrow_mut().select_day(new_date, extend) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = self.scroll_back(self.step);
if self.selection.borrow_mut().select_day(new_date, extend) {
r = CalOutcome::Selected;
}
}
} else {
let new_date = self.end_date();
if self.selection.borrow_mut().select_day(new_date, extend) {
r = CalOutcome::Selected;
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
pub fn next_day(&mut self, n: usize, extend: bool) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let new_date = if let Some(date) = self.selection.lead_selection() {
if date >= base_start && date <= base_end || self.step > 0 {
date + Days::new(n as u64)
} else if date < base_start {
self.start_date()
} else {
self.end_date()
}
} else {
self.start_date()
};
let mut r = CalOutcome::Continue;
if new_date >= base_start && new_date <= base_end {
if self.selection.borrow_mut().select_day(new_date, extend) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = self.scroll_forward(self.step);
if self.selection.borrow_mut().select_day(new_date, extend) {
r = CalOutcome::Selected;
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
pub fn prev_week(&mut self, n: usize, extend: bool) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let mut r = CalOutcome::Continue;
if let Some(date) = self.selection.lead_selection() {
let new_date = if date >= base_start && date <= base_end || self.step != 0 {
date - Days::new(7 * n as u64)
} else if date < base_start {
self.start_date()
} else {
self.end_date()
};
let new_date_end = new_date.week(Weekday::Mon).last_day();
if new_date_end >= base_start && new_date_end <= base_end {
if self.selection.borrow_mut().select_week(new_date, extend) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = self.scroll_back(self.step);
if self.selection.borrow_mut().select_week(new_date, extend) {
r = CalOutcome::Selected;
}
}
} else {
let new_date = self.end_date();
if self.selection.borrow_mut().select_week(new_date, extend) {
r = CalOutcome::Selected;
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
pub fn next_week(&mut self, n: usize, extend: bool) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let new_date = if let Some(date) = self.selection.lead_selection() {
let date_end = date.week(Weekday::Mon).last_day();
if date_end >= base_start && date_end <= base_end || self.step > 0 {
date + Days::new(7 * n as u64)
} else if date_end < base_start {
self.start_date()
} else {
self.end_date()
}
} else {
self.start_date()
};
let mut r = CalOutcome::Continue;
if new_date >= base_start && new_date <= base_end {
if self.selection.borrow_mut().select_week(new_date, extend) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = self.scroll_forward(self.step);
if self.selection.borrow_mut().select_week(new_date, extend) {
r = CalOutcome::Selected;
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
pub fn prev_month(&mut self, n: usize, extend: bool) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let mut r = CalOutcome::Continue;
if let Some(date) = self.selection.lead_selection() {
let new_date = if date >= base_start && date <= base_end || self.step != 0 {
date - Months::new(n as u32)
} else if date < base_start {
self.start_date()
} else {
self.end_date()
};
if new_date >= base_start && new_date <= base_end {
if self.selection.borrow_mut().select_month(new_date, extend) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = self.scroll_back(self.step);
if self.selection.borrow_mut().select_month(new_date, extend) {
r = CalOutcome::Selected;
}
}
} else {
let new_date = self.end_date();
if self.selection.borrow_mut().select_month(new_date, extend) {
r = CalOutcome::Selected;
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
pub fn next_month(&mut self, n: usize, extend: bool) -> CalOutcome {
let base_start = self.start_date();
let base_end = self.end_date();
let new_date = if let Some(date) = self.selection.lead_selection() {
if date >= base_start && date <= base_end || self.step > 0 {
date + Months::new(n as u32)
} else if date < base_start {
self.start_date()
} else {
self.end_date()
}
} else {
self.start_date()
};
let mut r = CalOutcome::Continue;
if new_date >= base_start && new_date <= base_end {
if self.selection.borrow_mut().select_month(new_date, extend) {
r = CalOutcome::Selected;
}
} else if self.step > 0 {
r = self.scroll_forward(self.step);
if self.selection.borrow_mut().select_month(new_date, extend) {
r = CalOutcome::Selected;
}
}
if r.is_consumed() {
self.focus_lead();
}
r
}
}