use crate::_private::NonExhaustive;
use crate::event::{ReadOnly, TextOutcome};
use crate::masked_input::{MaskedInput, MaskedInputState, MaskedInputStyle};
use chrono::format::{Fixed, Item, Numeric, Pad, StrftimeItems};
use chrono::{Datelike, Days, Local, Months, NaiveDate};
#[allow(unused_imports)]
use log::debug;
use rat_event::{ct_event, FocusKeys, HandleEvent, MouseOnly};
use rat_focus::{FocusFlag, HasFocusFlag};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Style;
use ratatui::widgets::{Block, StatefulWidget, StatefulWidgetRef};
use std::fmt;
use std::fmt::Debug;
use std::ops::Range;
use unicode_segmentation::UnicodeSegmentation;
#[derive(Debug, Default, Clone)]
pub struct DateInput<'a> {
widget: MaskedInput<'a>,
}
#[derive(Debug, Clone)]
pub struct DateInputState {
pub widget: MaskedInputState,
pattern: String,
locale: chrono::Locale,
pub non_exhaustive: NonExhaustive,
}
impl<'a> DateInput<'a> {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn show_compact(mut self, show_compact: bool) -> Self {
self.widget = self.widget.show_compact(show_compact);
self
}
#[inline]
pub fn styles(mut self, style: MaskedInputStyle) -> Self {
self.widget = self.widget.styles(style);
self
}
#[inline]
pub fn style(mut self, style: impl Into<Style>) -> Self {
self.widget = self.widget.style(style);
self
}
#[inline]
pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
self.widget = self.widget.focus_style(style);
self
}
#[inline]
pub fn select_style(mut self, style: impl Into<Style>) -> Self {
self.widget = self.widget.select_style(style);
self
}
#[inline]
pub fn invalid_style(mut self, style: impl Into<Style>) -> Self {
self.widget = self.widget.invalid_style(style);
self
}
#[inline]
pub fn block(mut self, block: Block<'a>) -> Self {
self.widget = self.widget.block(block);
self
}
}
impl<'a> StatefulWidgetRef for DateInput<'a> {
type State = DateInputState;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.widget.render_ref(area, buf, &mut state.widget);
}
}
impl<'a> StatefulWidget for DateInput<'a> {
type State = DateInputState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.widget.render(area, buf, &mut state.widget);
}
}
impl Default for DateInputState {
fn default() -> Self {
Self {
widget: Default::default(),
pattern: Default::default(),
locale: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl DateInputState {
pub fn new<S: AsRef<str>>(pattern: S) -> Result<Self, fmt::Error> {
let mut s = Self::default();
s.set_format(pattern)?;
Ok(s)
}
#[inline]
pub fn new_loc<S: AsRef<str>>(pattern: S, locale: chrono::Locale) -> Result<Self, fmt::Error> {
let mut s = Self::default();
s.set_format_loc(pattern, locale)?;
Ok(s)
}
#[inline]
pub fn format(&self) -> &str {
self.pattern.as_str()
}
#[inline]
pub fn locale(&self) -> chrono::Locale {
self.locale
}
#[inline]
pub fn set_format<S: AsRef<str>>(&mut self, pattern: S) -> Result<(), fmt::Error> {
self.set_format_loc(pattern, chrono::Locale::default())
}
#[inline]
pub fn set_format_loc<S: AsRef<str>>(
&mut self,
pattern: S,
locale: chrono::Locale,
) -> Result<(), fmt::Error> {
let mut mask = String::new();
let items = StrftimeItems::new_with_locale(pattern.as_ref(), locale)
.parse()
.map_err(|_| fmt::Error)?;
for t in &items {
match t {
Item::Literal(s) => {
for c in s.graphemes(true) {
mask.push('\\');
mask.push_str(c);
}
}
Item::OwnedLiteral(s) => {
for c in s.graphemes(true) {
mask.push('\\');
mask.push_str(c);
}
}
Item::Space(s) => {
for c in s.graphemes(true) {
mask.push_str(c);
}
}
Item::OwnedSpace(s) => {
for c in s.graphemes(true) {
mask.push_str(c);
}
}
Item::Numeric(v, Pad::None | Pad::Space) => match v {
Numeric::Year | Numeric::IsoYear => mask.push_str("9999"),
Numeric::YearDiv100
| Numeric::YearMod100
| Numeric::IsoYearDiv100
| Numeric::IsoYearMod100
| Numeric::Month
| Numeric::Day
| Numeric::WeekFromSun
| Numeric::WeekFromMon
| Numeric::IsoWeek
| Numeric::Hour
| Numeric::Hour12
| Numeric::Minute
| Numeric::Second => mask.push_str("99"),
Numeric::NumDaysFromSun | Numeric::WeekdayFromMon => mask.push('9'),
Numeric::Ordinal => mask.push_str("999"),
Numeric::Nanosecond => mask.push_str("999999999"),
Numeric::Timestamp => mask.push_str("###########"),
_ => return Err(fmt::Error),
},
Item::Numeric(v, Pad::Zero) => match v {
Numeric::Year | Numeric::IsoYear => mask.push_str("0000"),
Numeric::YearDiv100
| Numeric::YearMod100
| Numeric::IsoYearDiv100
| Numeric::IsoYearMod100
| Numeric::Month
| Numeric::Day
| Numeric::WeekFromSun
| Numeric::WeekFromMon
| Numeric::IsoWeek
| Numeric::Hour
| Numeric::Hour12
| Numeric::Minute
| Numeric::Second => mask.push_str("00"),
Numeric::NumDaysFromSun | Numeric::WeekdayFromMon => mask.push('0'),
Numeric::Ordinal => mask.push_str("000"),
Numeric::Nanosecond => mask.push_str("000000000"),
Numeric::Timestamp => mask.push_str("#0000000000"),
_ => return Err(fmt::Error),
},
Item::Fixed(v) => match v {
Fixed::ShortMonthName => mask.push_str("___"),
Fixed::LongMonthName => mask.push_str("_________"),
Fixed::ShortWeekdayName => mask.push_str("___"),
Fixed::LongWeekdayName => mask.push_str("________"),
Fixed::LowerAmPm => mask.push_str("__"),
Fixed::UpperAmPm => mask.push_str("__"),
Fixed::Nanosecond => mask.push_str(".#########"),
Fixed::Nanosecond3 => mask.push_str(".###"),
Fixed::Nanosecond6 => mask.push_str(".######"),
Fixed::Nanosecond9 => mask.push_str(".#########"),
Fixed::TimezoneName => mask.push_str("__________"),
Fixed::TimezoneOffsetColon | Fixed::TimezoneOffset => mask.push_str("+##:##"),
Fixed::TimezoneOffsetDoubleColon => mask.push_str("+##:##:##"),
Fixed::TimezoneOffsetTripleColon => mask.push_str("+##"),
Fixed::TimezoneOffsetColonZ | Fixed::TimezoneOffsetZ => return Err(fmt::Error),
Fixed::RFC2822 => {
return Err(fmt::Error);
}
Fixed::RFC3339 => {
return Err(fmt::Error);
}
_ => return Err(fmt::Error),
},
Item::Error => return Err(fmt::Error),
}
}
self.locale = locale;
self.pattern = pattern.as_ref().to_string();
self.widget.set_mask(mask)?;
Ok(())
}
#[inline]
pub fn set_focused(&mut self, focus: bool) {
self.widget.focus.focus.set(focus);
}
#[inline]
pub fn is_focused(&mut self) -> bool {
self.widget.focus.focus.get()
}
#[inline]
pub fn set_invalid(&mut self, invalid: bool) {
self.widget.invalid = invalid;
}
#[inline]
pub fn get_invalid(&self) -> bool {
self.widget.invalid
}
#[inline]
pub fn clear(&mut self) {
self.widget.clear();
}
#[inline]
pub fn offset(&self) -> usize {
self.widget.offset()
}
#[inline]
pub fn set_offset(&mut self, offset: usize) {
self.widget.set_offset(offset)
}
#[inline]
pub fn cursor(&self) -> usize {
self.widget.cursor()
}
#[inline]
pub fn set_cursor(&mut self, cursor: usize, extend_selection: bool) -> bool {
self.widget.set_cursor(cursor, extend_selection)
}
#[inline]
pub fn set_default_cursor(&mut self) {
self.widget.set_default_cursor()
}
#[inline]
pub fn anchor(&self) -> usize {
self.widget.anchor()
}
#[inline]
pub fn default_value(&self) -> String {
self.widget.default_value()
}
#[inline]
pub fn value(&self) -> Result<NaiveDate, chrono::ParseError> {
NaiveDate::parse_from_str(self.widget.compact_value().as_str(), self.pattern.as_str())
}
#[inline]
pub fn set_value(&mut self, date: NaiveDate) {
let v = date.format(self.pattern.as_str()).to_string();
self.widget.set_value(v);
}
#[inline]
pub fn is_empty(&self) -> bool {
self.widget.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.widget.len()
}
#[inline]
pub fn has_selection(&self) -> bool {
self.widget.has_selection()
}
#[inline]
pub fn selection(&self) -> Range<usize> {
self.widget.selection()
}
#[inline]
pub fn set_selection(&mut self, anchor: usize, cursor: usize) -> bool {
self.widget.set_selection(anchor, cursor)
}
#[inline]
pub fn select_all(&mut self) {
self.widget.select_all();
}
#[inline]
pub fn selected_value(&self) -> &str {
self.widget.selected_value()
}
#[inline]
pub fn insert_char(&mut self, c: char) -> bool {
self.widget.insert_char(c)
}
#[inline]
pub fn delete_range(&mut self, range: Range<usize>) -> bool {
self.widget.delete_range(range)
}
#[inline]
pub fn delete_next_word(&mut self) -> bool {
self.widget.delete_next_word()
}
#[inline]
pub fn delete_prev_word(&mut self) -> bool {
self.widget.delete_prev_word()
}
#[inline]
pub fn delete_prev_char(&mut self) -> bool {
self.widget.delete_prev_char()
}
#[inline]
pub fn delete_next_char(&mut self) -> bool {
self.widget.delete_next_char()
}
#[inline]
pub fn move_to_next_word(&mut self, extend_selection: bool) -> bool {
self.widget.move_to_next_word(extend_selection)
}
#[inline]
pub fn move_to_prev_word(&mut self, extend_selection: bool) -> bool {
self.widget.move_to_next_word(extend_selection)
}
#[inline]
pub fn move_to_next(&mut self, extend_selection: bool) -> bool {
self.widget.move_to_next(extend_selection)
}
#[inline]
pub fn move_to_prev(&mut self, extend_selection: bool) -> bool {
self.widget.move_to_prev(extend_selection)
}
#[inline]
pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
self.widget.move_to_line_start(extend_selection)
}
#[inline]
pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
self.widget.move_to_line_end(extend_selection)
}
pub fn to_screen_col(&self, pos: usize) -> Option<u16> {
self.widget.to_screen_col(pos)
}
pub fn from_screen_col(&self, x: usize) -> Option<usize> {
self.widget.from_screen_col(x)
}
#[inline]
pub fn set_screen_cursor(&mut self, cursor: isize, extend_selection: bool) -> bool {
self.widget.set_screen_cursor(cursor, extend_selection)
}
#[inline]
pub fn screen_cursor(&self) -> Option<(u16, u16)> {
self.widget.screen_cursor()
}
}
impl HasFocusFlag for DateInputState {
#[inline]
fn focus(&self) -> &FocusFlag {
&self.widget.focus
}
#[inline]
fn area(&self) -> Rect {
self.widget.area
}
}
#[derive(Debug)]
pub struct ConvenientKeys;
impl HandleEvent<crossterm::event::Event, ConvenientKeys, TextOutcome> for DateInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: ConvenientKeys) -> TextOutcome {
let r = if self.is_focused() {
match event {
ct_event!(key press 'h') => {
self.set_value(Local::now().date_naive());
TextOutcome::Changed
}
ct_event!(key press 'l') => {
let date = Local::now()
.date_naive()
.checked_sub_months(Months::new(1))
.expect("month")
.with_day(1)
.expect("day");
self.set_value(date);
TextOutcome::Changed
}
ct_event!(key press SHIFT-'L') => {
let date = Local::now()
.date_naive()
.with_day(1)
.expect("month")
.checked_sub_days(Days::new(1))
.expect("day");
self.set_value(date);
TextOutcome::Changed
}
ct_event!(key press 'm') => {
let date = Local::now().date_naive().with_day(1).expect("day");
self.set_value(date);
TextOutcome::Changed
}
ct_event!(key press SHIFT-'M') => {
let date = Local::now()
.date_naive()
.checked_add_months(Months::new(1))
.expect("month")
.with_day(1)
.expect("day")
.checked_sub_days(Days::new(1))
.expect("day");
self.set_value(date);
TextOutcome::Changed
}
ct_event!(key press 'n') => {
let date = Local::now()
.date_naive()
.checked_add_months(Months::new(1))
.expect("month")
.with_day(1)
.expect("day");
self.set_value(date);
TextOutcome::Changed
}
ct_event!(key press SHIFT-'N') => {
let date = Local::now()
.date_naive()
.checked_add_months(Months::new(2))
.expect("month")
.with_day(1)
.expect("day")
.checked_sub_days(Days::new(1))
.expect("day");
self.set_value(date);
TextOutcome::Changed
}
ct_event!(key press 'j') => {
if let Ok(date) = self.value() {
let date = date.checked_add_months(Months::new(1)).expect("month");
self.set_value(date);
}
TextOutcome::Changed
}
ct_event!(key press SHIFT-'J') => {
if let Ok(date) = self.value() {
let date = date.with_year(date.year() + 1).expect("year");
self.set_value(date);
}
TextOutcome::Changed
}
ct_event!(key press 'k') => {
if let Ok(date) = self.value() {
let date = date.checked_sub_months(Months::new(1)).expect("month");
self.set_value(date);
}
TextOutcome::Changed
}
ct_event!(key press SHIFT-'K') => {
if let Ok(date) = self.value() {
let date = date.with_year(date.year() - 1).expect("year");
self.set_value(date);
}
TextOutcome::Changed
}
ct_event!(key press 'a'|'b') => {
if let Ok(date) = self.value() {
let date = date.with_month(1).expect("month").with_day(1).expect("day");
self.set_value(date);
}
TextOutcome::Changed
}
ct_event!(key press 'e') => {
if let Ok(date) = self.value() {
let date = date
.with_month(12)
.expect("month")
.with_day(31)
.expect("day");
self.set_value(date);
}
TextOutcome::Changed
}
_ => TextOutcome::NotUsed,
}
} else {
TextOutcome::NotUsed
};
if r == TextOutcome::NotUsed {
self.handle(event, FocusKeys)
} else {
r
}
}
}
impl HandleEvent<crossterm::event::Event, FocusKeys, TextOutcome> for DateInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: FocusKeys) -> TextOutcome {
self.widget.handle(event, FocusKeys)
}
}
impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for DateInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
self.widget.handle(event, ReadOnly)
}
}
impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for DateInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
self.widget.handle(event, MouseOnly)
}
}
pub fn handle_events(
state: &mut DateInputState,
focus: bool,
event: &crossterm::event::Event,
) -> TextOutcome {
state.widget.focus.set(focus);
HandleEvent::handle(state, event, FocusKeys)
}
pub fn handle_readonly_events(
state: &mut DateInputState,
focus: bool,
event: &crossterm::event::Event,
) -> TextOutcome {
state.widget.focus.set(focus);
state.handle(event, ReadOnly)
}
pub fn handle_mouse_events(
state: &mut DateInputState,
event: &crossterm::event::Event,
) -> TextOutcome {
HandleEvent::handle(state, event, MouseOnly)
}