rat_widget/calendar/
month.rs

1use crate::_private::NonExhaustive;
2use crate::calendar::event::CalOutcome;
3use crate::calendar::selection::{NoSelection, RangeSelection, SingleSelection};
4use crate::calendar::style::CalendarStyle;
5use crate::calendar::{first_day_of_month, last_day_of_month, CalendarSelection};
6use crate::util::{block_size, revert_style};
7use chrono::{Datelike, Days, NaiveDate, Weekday};
8use rat_event::util::MouseFlagsN;
9use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
10use rat_reloc::RelocatableState;
11use ratatui::buffer::Buffer;
12use ratatui::layout::{Alignment, Rect};
13use ratatui::prelude::Style;
14use ratatui::style::Stylize;
15use ratatui::text::Span;
16use ratatui::widgets::block::Title;
17#[cfg(feature = "unstable-widget-ref")]
18use ratatui::widgets::StatefulWidgetRef;
19use ratatui::widgets::{Block, StatefulWidget, Widget};
20use std::cell::RefCell;
21use std::cmp::max;
22use std::collections::HashMap;
23use std::marker::PhantomData;
24use std::rc::Rc;
25
26/// Month widget.
27///
28/// Renders one month of a calendar.
29///
30/// There is movement and selection support within the month,
31/// but no scrolling. Use a Calendar for full calendar features.
32///
33/// ```rust ignore
34/// Month::new()
35///     .locale(Locale::de_AT_euro)
36///     .styles(THEME.month_style())
37///     .title_align(Alignment::Left)
38///     .day_styles(&date_styles)
39///     .show_weekdays()
40///     .block(Block::bordered().borders(Borders::TOP))
41///     .render(l2[2], frame.buffer_mut(), &mut state.month1);
42/// ```
43///
44/// ```rust ignore
45/// match state.month1.handle(event, Regular) {
46///     CalOutcome::Selected => {
47///         // doit
48///         Outcome::Changed
49///     }
50///     r => r.into()
51/// }
52/// ```
53///
54#[derive(Debug, Clone)]
55pub struct Month<'a, Selection> {
56    /// Start date of the month.
57    start_date: Option<NaiveDate>,
58
59    /// Base style.
60    style: Style,
61    /// Title style.
62    title_style: Option<Style>,
63    /// Title align.
64    title_align: Alignment,
65    /// Week number style.
66    weeknum_style: Option<Style>,
67    /// Week day style.
68    weekday_style: Option<Style>,
69    /// Default day style.
70    day_style: Option<Style>,
71    /// Styling for a single date.
72    day_styles: Option<&'a HashMap<NaiveDate, Style>>,
73    /// Selection
74    select_style: Option<Style>,
75    /// Focus
76    focus_style: Option<Style>,
77
78    /// Show month name
79    show_month: bool,
80    /// Show Weekdays above
81    show_weekdays: bool,
82
83    /// Block
84    block: Option<Block<'a>>,
85
86    /// Locale
87    loc: chrono::Locale,
88
89    phantom: PhantomData<Selection>,
90}
91
92/// State & event-handling.
93#[derive(Debug)]
94pub struct MonthState<Selection = SingleSelection> {
95    /// Total area.
96    /// __readonly__. renewed for each render.
97    pub area: Rect,
98    /// Area inside the border.
99    /// __readonly__. renewed for each render.
100    pub inner: Rect,
101    /// Area of the main calendar.
102    /// __readonly__. renewed for each render.
103    pub area_cal: Rect,
104    /// Area for the days of the month.
105    /// __readonly__. renewed for each render.
106    pub area_days: [Rect; 31],
107    /// Area for all the week numbers.
108    /// __readonly__. renewed for each render.
109    pub area_weeknum: Rect,
110    /// Area for the week numbers.
111    /// __readonly__. renewed for each render.
112    pub area_weeks: [Rect; 6],
113
114    /// Startdate
115    start_date: NaiveDate,
116
117    /// Selection model.
118    /// The selection model can be shared with other Month widgets.
119    pub selection: Rc<RefCell<Selection>>,
120
121    /// Set to the container-focus if part of a container.
122    /// __read+write__
123    pub container: Option<FocusFlag>,
124
125    /// Focus
126    /// __read+write__
127    pub focus: FocusFlag,
128    /// Mouse flags
129    /// __read+write__
130    pub mouse: MouseFlagsN,
131
132    pub non_exhaustive: NonExhaustive,
133}
134
135impl<Selection> Default for Month<'_, Selection> {
136    fn default() -> Self {
137        Self {
138            start_date: None,
139            style: Default::default(),
140            title_style: Default::default(),
141            title_align: Default::default(),
142            weeknum_style: Default::default(),
143            weekday_style: Default::default(),
144            day_style: Default::default(),
145            day_styles: Default::default(),
146            select_style: Default::default(),
147            focus_style: Default::default(),
148            show_month: true,
149            show_weekdays: true,
150            block: Default::default(),
151            loc: Default::default(),
152            phantom: PhantomData,
153        }
154    }
155}
156
157impl<'a, Selection> Month<'a, Selection> {
158    /// New calendar.
159    pub fn new() -> Self {
160        Self::default()
161    }
162
163    /// Sets the starting date. This can be any date of the month.
164    /// If no date is set, the start_date of the state is used.
165    #[inline]
166    pub fn date(mut self, s: NaiveDate) -> Self {
167        self.start_date = Some(first_day_of_month(s));
168        self
169    }
170
171    /// Locale for month-names, day-names.
172    #[inline]
173    pub fn locale(mut self, loc: chrono::Locale) -> Self {
174        self.loc = loc;
175        self
176    }
177
178    /// Show month title
179    #[inline]
180    pub fn show_show_month(mut self) -> Self {
181        self.show_month = true;
182        self
183    }
184
185    /// Show weekday titles
186    #[inline]
187    pub fn show_weekdays(mut self) -> Self {
188        self.show_weekdays = true;
189        self
190    }
191
192    /// Set the composite style.
193    #[inline]
194    pub fn styles(mut self, s: CalendarStyle) -> Self {
195        self.style = s.style;
196        if s.title.is_some() {
197            self.title_style = s.title;
198        }
199        if s.weeknum.is_some() {
200            self.weeknum_style = s.weeknum;
201        }
202        if s.weekday.is_some() {
203            self.weekday_style = s.weekday;
204        }
205        if s.day.is_some() {
206            self.day_style = s.day;
207        }
208        if s.select.is_some() {
209            self.select_style = s.select;
210        }
211        if s.focus.is_some() {
212            self.focus_style = s.focus;
213        }
214        if s.block.is_some() {
215            self.block = s.block;
216        }
217        self.block = self.block.map(|v| v.style(self.style));
218        self
219    }
220
221    /// Style for the selection
222    pub fn select_style(mut self, style: Style) -> Self {
223        self.select_style = Some(style);
224        self
225    }
226
227    /// Style for the focus.
228    pub fn focus_style(mut self, style: Style) -> Self {
229        self.focus_style = Some(style);
230        self
231    }
232
233    /// Sets the default day-style.
234    #[inline]
235    pub fn day_style(mut self, s: impl Into<Style>) -> Self {
236        self.day_style = Some(s.into());
237        self
238    }
239
240    /// Set a map date->Style for highlighting some dates.
241    #[inline]
242    pub fn day_styles(mut self, styles: &'a HashMap<NaiveDate, Style>) -> Self {
243        self.day_styles = Some(styles);
244        self
245    }
246
247    /// Set the week number style.
248    #[inline]
249    pub fn week_style(mut self, s: impl Into<Style>) -> Self {
250        self.weeknum_style = Some(s.into());
251        self
252    }
253
254    /// Set the week day style.
255    #[inline]
256    pub fn weekday_style(mut self, s: impl Into<Style>) -> Self {
257        self.weekday_style = Some(s.into());
258        self
259    }
260
261    /// Set the month-name style.
262    #[inline]
263    pub fn title_style(mut self, s: impl Into<Style>) -> Self {
264        self.title_style = Some(s.into());
265        self
266    }
267
268    /// Set the month-name align.
269    #[inline]
270    pub fn title_align(mut self, a: Alignment) -> Self {
271        self.title_align = a;
272        self
273    }
274
275    /// Block
276    #[inline]
277    pub fn block(mut self, b: Block<'a>) -> Self {
278        self.block = Some(b);
279        self.block = self.block.map(|v| v.style(self.style));
280        self
281    }
282
283    /// Inherent width of the widget.
284    #[inline]
285    pub fn width(&self) -> u16 {
286        8 * 3 + block_size(&self.block).width
287    }
288
289    /// Inherent height for the widget.
290    /// Can vary with the number of months.
291    #[inline]
292    pub fn height(&self, state: &MonthState<Selection>) -> u16 {
293        let start_date = if let Some(start_date) = self.start_date {
294            start_date
295        } else {
296            state.start_date
297        };
298
299        let r = MonthState::<Selection>::count_weeks(start_date) as u16;
300        let w = if self.show_weekdays { 1 } else { 0 };
301        let b = max(1, block_size(&self.block).height);
302        r + w + b
303    }
304}
305
306#[cfg(feature = "unstable-widget-ref")]
307impl<'a, Selection> StatefulWidgetRef for Month<'a, Selection>
308where
309    Selection: CalendarSelection,
310{
311    type State = MonthState<Selection>;
312
313    fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
314        render_ref(self, area, buf, state);
315    }
316}
317
318impl<Selection> StatefulWidget for Month<'_, Selection>
319where
320    Selection: CalendarSelection,
321{
322    type State = MonthState<Selection>;
323
324    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
325        render_ref(&self, area, buf, state);
326    }
327}
328
329fn render_ref<Selection: CalendarSelection>(
330    widget: &Month<'_, Selection>,
331    area: Rect,
332    buf: &mut Buffer,
333    state: &mut MonthState<Selection>,
334) {
335    state.area = area;
336    if let Some(start_date) = widget.start_date {
337        state.start_date = start_date;
338    }
339
340    let mut day = state.start_date;
341
342    let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
343    let select_style = if let Some(select_style) = widget.select_style {
344        if state.is_container_focused() || state.is_focused() {
345            focus_style
346        } else {
347            select_style
348        }
349    } else {
350        if state.is_container_focused() || state.is_focused() {
351            focus_style
352        } else {
353            revert_style(widget.style)
354        }
355    };
356    let day_style = widget.day_style.unwrap_or(widget.style);
357    let week_style = widget.weeknum_style.unwrap_or(widget.style);
358    let weekday_style = widget.weekday_style.unwrap_or(widget.style);
359
360    let title_style = if let Some(title_style) = widget.title_style {
361        title_style
362    } else {
363        widget.style
364    };
365    let title_style = if state.is_focused() {
366        title_style.patch(focus_style)
367    } else {
368        title_style
369    };
370
371    let block = if widget.show_month {
372        if let Some(block) = widget.block.clone() {
373            block
374                .title(Title::from(
375                    day.format_localized("%B", widget.loc).to_string(),
376                ))
377                .title_style(title_style)
378                .title_alignment(widget.title_align)
379        } else {
380            Block::new()
381                .style(widget.style)
382                .title(Title::from(
383                    day.format_localized("%B", widget.loc).to_string(),
384                ))
385                .title_style(title_style)
386                .title_alignment(widget.title_align)
387        }
388    } else {
389        if let Some(block) = widget.block.clone() {
390            block
391        } else {
392            Block::new().style(widget.style)
393        }
394    };
395    state.inner = block.inner(area);
396    block.render(area, buf);
397
398    let month = day.month();
399    let mut w = 0;
400    let mut x = state.inner.x;
401    let mut y = state.inner.y;
402
403    // week days
404    if widget.show_weekdays {
405        let mut week_0 = day.week(Weekday::Mon).first_day();
406
407        x += 3;
408        buf.set_style(Rect::new(x, y, 3 * 7, 1), weekday_style);
409        for _ in 0..7 {
410            let area = Rect::new(x, y, 2, 1).intersection(state.inner);
411
412            let day_name = week_0.format_localized("%a", widget.loc).to_string();
413            Span::from(format!("{:2} ", day_name)).render(area, buf);
414
415            x += 3;
416            week_0 = week_0 + Days::new(1);
417        }
418        x = state.inner.x;
419        y += 1;
420    }
421
422    // reset areas
423    for i in 0..31 {
424        state.area_days[i] = Rect::default();
425    }
426    for i in 0..6 {
427        state.area_weeks[i] = Rect::default();
428    }
429    state.area_cal = Rect::new(x + 3, y, 7 * 3, state.week_len() as u16);
430    state.area_weeknum = Rect::new(x, y, 3, state.week_len() as u16);
431
432    // first line may omit a few days
433    state.area_weeks[w] = Rect::new(x, y, 2, 1).intersection(state.inner);
434    Span::from(day.format_localized("%V", widget.loc).to_string())
435        .style(week_style)
436        .render(state.area_weeks[w], buf);
437
438    x += 3;
439
440    for wd in [
441        Weekday::Mon,
442        Weekday::Tue,
443        Weekday::Wed,
444        Weekday::Thu,
445        Weekday::Fri,
446        Weekday::Sat,
447        Weekday::Sun,
448    ] {
449        if day.weekday() != wd {
450            x += 3;
451        } else {
452            let day_style = calc_day_style(widget, state, day, day_style, select_style);
453            state.area_days[day.day0() as usize] = Rect::new(x, y, 2, 1).intersection(state.inner);
454
455            Span::from(day.format_localized("%e", widget.loc).to_string())
456                .style(day_style)
457                .render(state.area_days[day.day0() as usize], buf);
458
459            if wd != Weekday::Sun && state.selection.is_selected(day + Days::new(1)) {
460                let mut gap_area = state.area_days[day.day0() as usize];
461                gap_area.x += 2;
462                gap_area.width = 1;
463                Span::from(" ").style(day_style).render(gap_area, buf);
464            }
465
466            x += 3;
467            day = day + Days::new(1);
468        }
469    }
470
471    w += 1;
472    x = state.inner.x;
473    y += 1;
474
475    while month == day.month() {
476        state.area_weeks[w] = Rect::new(x, y, 2, 1).intersection(state.inner);
477        Span::from(day.format_localized("%V", widget.loc).to_string())
478            .style(week_style)
479            .render(state.area_weeks[w], buf);
480
481        x += 3;
482
483        for wd in [
484            Weekday::Mon,
485            Weekday::Tue,
486            Weekday::Wed,
487            Weekday::Thu,
488            Weekday::Fri,
489            Weekday::Sat,
490            Weekday::Sun,
491        ] {
492            if day.month() == month {
493                let day_style = calc_day_style(widget, state, day, day_style, select_style);
494
495                state.area_days[day.day0() as usize] =
496                    Rect::new(x, y, 2, 1).intersection(state.inner);
497
498                Span::from(day.format_localized("%e", widget.loc).to_string())
499                    .style(day_style)
500                    .render(state.area_days[day.day0() as usize], buf);
501
502                if wd != Weekday::Sun && state.selection.is_selected(day + Days::new(1)) {
503                    let mut gap_area = state.area_days[day.day0() as usize];
504                    gap_area.x += 2;
505                    gap_area.width = 1;
506                    Span::from(" ").style(day_style).render(gap_area, buf);
507                }
508
509                x += 3;
510                day = day + Days::new(1);
511            } else {
512                x += 3;
513            }
514        }
515
516        w += 1;
517        x = state.inner.x;
518        y += 1;
519    }
520}
521
522fn calc_day_style<Selection: CalendarSelection>(
523    widget: &Month<'_, Selection>,
524    state: &mut MonthState<Selection>,
525    day: NaiveDate,
526    day_style: Style,
527    select_style: Style,
528) -> Style {
529    let day_style = if let Some(day_styles) = widget.day_styles {
530        if let Some(day_style) = day_styles.get(&day) {
531            *day_style
532        } else {
533            day_style
534        }
535    } else {
536        day_style
537    };
538
539    if (state.is_container_focused() || state.is_focused())
540        && state.selection.lead_selection() == Some(day)
541    {
542        day_style.patch(select_style.underlined())
543    } else if state.selection.is_selected(day) {
544        day_style.patch(select_style)
545    } else {
546        day_style
547    }
548}
549
550impl<Selection> HasFocus for MonthState<Selection> {
551    fn build(&self, builder: &mut FocusBuilder) {
552        builder.leaf_widget(self);
553    }
554
555    #[inline]
556    fn focus(&self) -> FocusFlag {
557        self.focus.clone()
558    }
559
560    #[inline]
561    fn area(&self) -> Rect {
562        self.area
563    }
564}
565
566impl<Selection> RelocatableState for MonthState<Selection> {
567    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
568        self.area.relocate(shift, clip);
569        self.inner.relocate(shift, clip);
570        self.area_cal.relocate(shift, clip);
571        self.area_weeknum.relocate(shift, clip);
572        self.area_days.relocate(shift, clip);
573        self.area_weeks.relocate(shift, clip);
574    }
575}
576
577impl<Selection> Clone for MonthState<Selection>
578where
579    Selection: Clone,
580{
581    fn clone(&self) -> Self {
582        Self {
583            area: self.area,
584            inner: self.inner,
585            area_cal: self.area_cal.clone(),
586            area_days: self.area_days.clone(),
587            area_weeknum: self.area_weeknum.clone(),
588            area_weeks: self.area_weeks.clone(),
589            start_date: self.start_date,
590            selection: self.selection.clone(),
591            container: self.container.clone(),
592            focus: FocusFlag::named(self.focus.name()),
593            mouse: Default::default(),
594            non_exhaustive: NonExhaustive,
595        }
596    }
597}
598
599impl<Selection> Default for MonthState<Selection>
600where
601    Selection: Default,
602{
603    fn default() -> Self {
604        Self {
605            area: Default::default(),
606            inner: Default::default(),
607            area_cal: Default::default(),
608            area_days: Default::default(),
609            area_weeknum: Default::default(),
610            area_weeks: Default::default(),
611            start_date: Default::default(),
612            selection: Default::default(),
613            container: Default::default(),
614            focus: Default::default(),
615            mouse: Default::default(),
616            non_exhaustive: NonExhaustive,
617        }
618    }
619}
620
621impl<Selection> MonthState<Selection> {
622    pub fn new() -> Self
623    where
624        Selection: Default,
625    {
626        Self::default()
627    }
628
629    pub fn named(name: &str) -> Self
630    where
631        Selection: Default,
632    {
633        Self {
634            focus: FocusFlag::named(name),
635            ..Self::default()
636        }
637    }
638
639    /// Sets the start-date of the calendar. You can set every date, it
640    /// will always be changed to the first of the month.
641    ///
642    /// Setting this will be useless if the date is set with the Month widget.
643    pub fn set_start_date(&mut self, date: NaiveDate) -> bool {
644        let old_value = self.start_date;
645        self.start_date = first_day_of_month(date);
646        old_value != self.start_date
647    }
648
649    /// Start date of this month. Will always be the first.
650    pub fn start_date(&self) -> NaiveDate {
651        self.start_date
652    }
653
654    /// End date of this month.
655    pub fn end_date(&self) -> NaiveDate {
656        last_day_of_month(self.start_date)
657    }
658
659    fn in_range(&self, date: NaiveDate) -> bool {
660        date >= self.start_date() && date <= self.end_date()
661    }
662
663    /// Nr of weeks in this month.
664    pub fn week_len(&self) -> usize {
665        Self::count_weeks(self.start_date)
666    }
667
668    /// Nr of weeks for the given month
669    pub fn count_weeks(day: NaiveDate) -> usize {
670        let mut day = day.with_day0(0).expect("date");
671        let month = day.month();
672
673        let mut weeks = 1;
674        for weekday in [
675            Weekday::Mon,
676            Weekday::Tue,
677            Weekday::Wed,
678            Weekday::Thu,
679            Weekday::Fri,
680            Weekday::Sat,
681            Weekday::Sun,
682        ] {
683            // run through first week
684            if day.weekday() == weekday {
685                day = day + Days::new(1);
686            }
687        }
688        // count mondays
689        while month == day.month() {
690            weeks += 1;
691            day = day + Days::new(7);
692        }
693
694        weeks
695    }
696
697    // is there a container for this month?
698    fn is_container_focused(&self) -> bool {
699        self.container
700            .as_ref()
701            .map(|v| v.is_focused())
702            .unwrap_or(false)
703    }
704}
705
706impl<Selection> MonthState<Selection>
707where
708    Selection: CalendarSelection,
709{
710    /// Lead selection
711    pub fn lead_selection(&self) -> Option<NaiveDate> {
712        self.selection.lead_selection()
713    }
714}
715
716impl MonthState<NoSelection> {}
717
718impl MonthState<SingleSelection> {
719    /// Removes all selection.
720    pub fn clear_selection(&mut self) {
721        self.selection.borrow_mut().clear();
722    }
723
724    /// Select a day by index.
725    pub fn select_day(&mut self, n: usize) -> CalOutcome {
726        if let Some(date) = self.start_date.with_day0(n as u32) {
727            if self.selection.borrow_mut().select(date) {
728                CalOutcome::Selected
729            } else {
730                CalOutcome::Continue
731            }
732        } else {
733            CalOutcome::Continue
734        }
735    }
736
737    /// Select the last day of the month.
738    pub fn select_last(&mut self) -> CalOutcome {
739        let date = self.end_date();
740        if self.selection.borrow_mut().select(date) {
741            CalOutcome::Selected
742        } else {
743            CalOutcome::Continue
744        }
745    }
746
747    /// Select by date.
748    /// Returns true if the date is valid for this month.
749    /// If false it doesn't change the selection.
750    pub fn select_date(&mut self, d: NaiveDate) -> bool {
751        let start = self.start_date;
752        if d.year() == start.year() && d.month() == start.month() {
753            self.selection.borrow_mut().select(d)
754        } else {
755            false
756        }
757    }
758
759    /// Lead selected day
760    pub fn selected_date(&self) -> Option<NaiveDate> {
761        self.selection.lead_selection()
762    }
763
764    /// Select previous day.
765    pub fn prev_day(&mut self, n: usize) -> CalOutcome {
766        let base_start = self.start_date();
767        let base_end = self.end_date();
768
769        let date = if let Some(date) = self.selection.lead_selection() {
770            if date >= base_start && date <= base_end {
771                date - Days::new(n as u64)
772            } else if date < base_start {
773                self.start_date()
774            } else {
775                self.end_date()
776            }
777        } else {
778            self.end_date()
779        };
780
781        if self.in_range(date) {
782            if self.selection.borrow_mut().select(date) {
783                CalOutcome::Selected
784            } else {
785                CalOutcome::Continue
786            }
787        } else {
788            CalOutcome::Continue
789        }
790    }
791
792    /// Select next day.
793    pub fn next_day(&mut self, n: usize) -> CalOutcome {
794        let base_start = self.start_date();
795        let base_end = self.end_date();
796
797        let date = if let Some(date) = self.selection.lead_selection() {
798            if date >= base_start && date <= base_end {
799                date + Days::new(n as u64)
800            } else if date < base_start {
801                self.start_date()
802            } else {
803                self.end_date()
804            }
805        } else {
806            self.start_date()
807        };
808
809        if self.in_range(date) {
810            if self.selection.borrow_mut().select(date) {
811                CalOutcome::Selected
812            } else {
813                CalOutcome::Continue
814            }
815        } else {
816            CalOutcome::Continue
817        }
818    }
819}
820
821impl MonthState<RangeSelection> {
822    /// Removes all selection.
823    pub fn clear_selection(&mut self) {
824        self.selection.borrow_mut().clear();
825    }
826
827    /// Select a week by index.
828    pub fn select_week(&mut self, n: usize, extend: bool) -> CalOutcome {
829        if n < self.week_len() {
830            let date = self.start_date() + Days::new(7 * n as u64);
831            if self.selection.borrow_mut().select_week(date, extend) {
832                CalOutcome::Selected
833            } else {
834                CalOutcome::Continue
835            }
836        } else {
837            CalOutcome::Continue
838        }
839    }
840
841    /// Select a day by index.
842    pub fn select_day(&mut self, n: usize, extend: bool) -> CalOutcome {
843        if let Some(date) = self.start_date.with_day0(n as u32) {
844            if self.selection.borrow_mut().select_day(date, extend) {
845                CalOutcome::Selected
846            } else {
847                CalOutcome::Continue
848            }
849        } else {
850            CalOutcome::Continue
851        }
852    }
853
854    /// Select the last day
855    pub fn select_last(&mut self, extend: bool) -> CalOutcome {
856        let date = self.end_date();
857        if self.selection.borrow_mut().select_day(date, extend) {
858            CalOutcome::Selected
859        } else {
860            CalOutcome::Continue
861        }
862    }
863
864    /// Select a week by date
865    /// Returns true if the date is valid for this month.
866    /// If false it doesn't change the selection.
867    pub fn select_week_by_date(&mut self, date: NaiveDate, extend: bool) -> bool {
868        let base = self.start_date;
869
870        let start = date.week(Weekday::Mon).first_day();
871        let end = date.week(Weekday::Mon).last_day();
872
873        if (start.year() == base.year() && start.month() == base.month())
874            || (end.year() == base.year() && end.month() == base.month())
875        {
876            self.selection.borrow_mut().select_week(start, extend)
877        } else {
878            false
879        }
880    }
881
882    /// Select by date.
883    /// Returns true if the date is valid for this month.
884    /// If false it doesn't change the selection.
885    pub fn select_date(&mut self, d: NaiveDate, extend: bool) -> bool {
886        let base = self.start_date;
887        if d.year() == base.year() && d.month() == base.month() {
888            self.selection.borrow_mut().select_day(d, extend)
889        } else {
890            false
891        }
892    }
893
894    /// Lead selected day
895    pub fn selected_date(&self) -> Option<NaiveDate> {
896        self.selection.lead_selection()
897    }
898
899    /// Select previous day.
900    pub fn prev_day(&mut self, n: usize, extend: bool) -> CalOutcome {
901        let base_start = self.start_date();
902        let base_end = self.end_date();
903
904        let date = if let Some(date) = self.selection.lead_selection() {
905            if date >= base_start && date <= base_end {
906                date - Days::new(n as u64)
907            } else if date < base_start {
908                self.start_date()
909            } else {
910                self.end_date()
911            }
912        } else {
913            self.end_date()
914        };
915
916        if self.in_range(date) {
917            if self.selection.borrow_mut().select_day(date, extend) {
918                CalOutcome::Selected
919            } else {
920                CalOutcome::Continue
921            }
922        } else {
923            CalOutcome::Continue
924        }
925    }
926
927    /// Select previous day.
928    pub fn next_day(&mut self, n: usize, extend: bool) -> CalOutcome {
929        let base_start = self.start_date();
930        let base_end = self.end_date();
931
932        let date = if let Some(date) = self.selection.lead_selection() {
933            if date >= base_start && date <= base_end {
934                date + Days::new(n as u64)
935            } else if date < base_start {
936                self.start_date()
937            } else {
938                self.end_date()
939            }
940        } else {
941            self.start_date()
942        };
943
944        if self.in_range(date) {
945            if self.selection.borrow_mut().select_day(date, extend) {
946                CalOutcome::Selected
947            } else {
948                CalOutcome::Continue
949            }
950        } else {
951            CalOutcome::Continue
952        }
953    }
954
955    /// Select previous week.
956    pub fn prev_week(&mut self, n: usize, extend: bool) -> CalOutcome {
957        let base_start = self.start_date();
958        let base_end = self.end_date();
959
960        if let Some(date) = self.selection.lead_selection() {
961            let new_date = if date >= base_start && date <= base_end {
962                date - Days::new(7 * n as u64)
963            } else if date < base_start {
964                self.start_date()
965            } else {
966                self.end_date()
967            };
968            let new_date_end = new_date.week(Weekday::Mon).last_day();
969            if new_date_end >= base_start && new_date_end <= base_end {
970                if self.selection.borrow_mut().select_week(new_date, extend) {
971                    CalOutcome::Selected
972                } else {
973                    CalOutcome::Continue
974                }
975            } else {
976                CalOutcome::Continue
977            }
978        } else {
979            let new_date = self.end_date();
980            if self.selection.borrow_mut().select_week(new_date, extend) {
981                CalOutcome::Selected
982            } else {
983                CalOutcome::Continue
984            }
985        }
986    }
987
988    /// Select previous day.
989    pub fn next_week(&mut self, n: usize, extend: bool) -> CalOutcome {
990        let start = self.start_date();
991        let end = self.end_date();
992
993        let new_date = if let Some(date) = self.selection.lead_selection() {
994            let date_end = date.week(Weekday::Mon).last_day();
995            if date_end >= start && date_end <= end {
996                date + Days::new(7 * n as u64)
997            } else if date_end < start {
998                self.start_date()
999            } else {
1000                self.end_date()
1001            }
1002        } else {
1003            self.start_date()
1004        };
1005
1006        if new_date >= start && new_date <= end {
1007            if self.selection.borrow_mut().select_week(new_date, extend) {
1008                CalOutcome::Selected
1009            } else {
1010                CalOutcome::Continue
1011            }
1012        } else {
1013            CalOutcome::Continue
1014        }
1015    }
1016}