1use crate::_private::NonExhaustive;
6use crate::clipboard::Clipboard;
7use crate::event::{ReadOnly, TextOutcome};
8use crate::text_input_mask::{MaskedInput, MaskedInputState};
9use crate::undo_buffer::{UndoBuffer, UndoEntry};
10use crate::{HasScreenCursor, TextError, TextFocusGained, TextFocusLost, TextStyle, upos_type};
11use chrono::NaiveDate;
12use chrono::format::{Fixed, Item, Numeric, Pad, StrftimeItems};
13use rat_event::{HandleEvent, MouseOnly, Regular};
14use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
15use rat_reloc::RelocatableState;
16use ratatui::buffer::Buffer;
17use ratatui::layout::Rect;
18use ratatui::style::Style;
19use ratatui::widgets::Block;
20use ratatui::widgets::StatefulWidget;
21use std::fmt;
22use std::ops::Range;
23use unicode_segmentation::UnicodeSegmentation;
24
25#[derive(Debug, Default, Clone)]
31pub struct DateInput<'a> {
32 widget: MaskedInput<'a>,
33}
34
35#[derive(Debug, Clone)]
38pub struct DateInputState {
39 pub widget: MaskedInputState,
41 pattern: String,
43 locale: chrono::Locale,
45
46 pub non_exhaustive: NonExhaustive,
47}
48
49impl<'a> DateInput<'a> {
50 pub fn new() -> Self {
51 Self::default()
52 }
53
54 #[inline]
56 pub fn compact(mut self, compact: bool) -> Self {
57 self.widget = self.widget.compact(compact);
58 self
59 }
60
61 #[inline]
63 pub fn styles(mut self, style: TextStyle) -> Self {
64 self.widget = self.widget.styles(style);
65 self
66 }
67
68 #[inline]
70 pub fn style(mut self, style: impl Into<Style>) -> Self {
71 self.widget = self.widget.style(style);
72 self
73 }
74
75 #[inline]
77 pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
78 self.widget = self.widget.focus_style(style);
79 self
80 }
81
82 #[inline]
84 pub fn select_style(mut self, style: impl Into<Style>) -> Self {
85 self.widget = self.widget.select_style(style);
86 self
87 }
88
89 #[inline]
91 pub fn invalid_style(mut self, style: impl Into<Style>) -> Self {
92 self.widget = self.widget.invalid_style(style);
93 self
94 }
95
96 #[inline]
98 pub fn block(mut self, block: Block<'a>) -> Self {
99 self.widget = self.widget.block(block);
100 self
101 }
102
103 #[inline]
105 pub fn on_focus_gained(mut self, of: TextFocusGained) -> Self {
106 self.widget = self.widget.on_focus_gained(of);
107 self
108 }
109
110 #[inline]
112 pub fn on_focus_lost(mut self, of: TextFocusLost) -> Self {
113 self.widget = self.widget.on_focus_lost(of);
114 self
115 }
116}
117
118impl<'a> StatefulWidget for &DateInput<'a> {
119 type State = DateInputState;
120
121 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
122 (&self.widget).render(area, buf, &mut state.widget);
123 }
124}
125
126impl StatefulWidget for DateInput<'_> {
127 type State = DateInputState;
128
129 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
130 self.widget.render(area, buf, &mut state.widget);
131 }
132}
133
134impl Default for DateInputState {
135 fn default() -> Self {
136 Self {
137 widget: Default::default(),
138 pattern: Default::default(),
139 locale: Default::default(),
140 non_exhaustive: NonExhaustive,
141 }
142 }
143}
144
145impl HasFocus for DateInputState {
146 fn build(&self, builder: &mut FocusBuilder) {
147 builder.leaf_widget(self);
148 }
149
150 #[inline]
151 fn focus(&self) -> FocusFlag {
152 self.widget.focus.clone()
153 }
154
155 #[inline]
156 fn area(&self) -> Rect {
157 self.widget.area
158 }
159
160 #[inline]
161 fn navigable(&self) -> Navigation {
162 self.widget.navigable()
163 }
164}
165
166impl DateInputState {
167 pub fn new() -> Self {
169 Self::default()
170 }
171
172 pub fn named(name: &str) -> Self {
173 Self {
174 widget: MaskedInputState::named(name),
175 ..Default::default()
176 }
177 }
178
179 pub fn with_pattern<S: AsRef<str>>(mut self, pattern: S) -> Result<Self, fmt::Error> {
181 self.set_format(pattern)?;
182 Ok(self)
183 }
184
185 #[inline]
187 pub fn with_loc_pattern<S: AsRef<str>>(
188 mut self,
189 pattern: S,
190 locale: chrono::Locale,
191 ) -> Result<Self, fmt::Error> {
192 self.set_format_loc(pattern, locale)?;
193 Ok(self)
194 }
195
196 #[inline]
198 pub fn format(&self) -> &str {
199 self.pattern.as_str()
200 }
201
202 #[inline]
204 pub fn locale(&self) -> chrono::Locale {
205 self.locale
206 }
207
208 #[inline]
213 pub fn set_format<S: AsRef<str>>(&mut self, pattern: S) -> Result<(), fmt::Error> {
214 self.set_format_loc(pattern, chrono::Locale::default())
215 }
216
217 #[inline]
222 pub fn set_format_loc<S: AsRef<str>>(
223 &mut self,
224 pattern: S,
225 locale: chrono::Locale,
226 ) -> Result<(), fmt::Error> {
227 let mut mask = String::new();
228 let items = StrftimeItems::new_with_locale(pattern.as_ref(), locale)
229 .parse()
230 .map_err(|_| fmt::Error)?;
231 for t in &items {
232 match t {
233 Item::Literal(s) => {
234 for c in s.graphemes(true) {
235 mask.push('\\');
236 mask.push_str(c);
237 }
238 }
239 Item::OwnedLiteral(s) => {
240 for c in s.graphemes(true) {
241 mask.push('\\');
242 mask.push_str(c);
243 }
244 }
245 Item::Space(s) => {
246 for c in s.graphemes(true) {
247 mask.push_str(c);
248 }
249 }
250 Item::OwnedSpace(s) => {
251 for c in s.graphemes(true) {
252 mask.push_str(c);
253 }
254 }
255 Item::Numeric(v, Pad::None | Pad::Space) => match v {
256 Numeric::Year | Numeric::IsoYear => mask.push_str("9999"),
257 Numeric::YearDiv100
258 | Numeric::YearMod100
259 | Numeric::IsoYearDiv100
260 | Numeric::IsoYearMod100
261 | Numeric::Month
262 | Numeric::Day
263 | Numeric::WeekFromSun
264 | Numeric::WeekFromMon
265 | Numeric::IsoWeek
266 | Numeric::Hour
267 | Numeric::Hour12
268 | Numeric::Minute
269 | Numeric::Second => mask.push_str("99"),
270 Numeric::NumDaysFromSun | Numeric::WeekdayFromMon => mask.push('9'),
271 Numeric::Ordinal => mask.push_str("999"),
272 Numeric::Nanosecond => mask.push_str("999999999"),
273 Numeric::Timestamp => mask.push_str("###########"),
274 _ => return Err(fmt::Error),
275 },
276 Item::Numeric(v, Pad::Zero) => match v {
277 Numeric::Year | Numeric::IsoYear => mask.push_str("0000"),
278 Numeric::YearDiv100
279 | Numeric::YearMod100
280 | Numeric::IsoYearDiv100
281 | Numeric::IsoYearMod100
282 | Numeric::Month
283 | Numeric::Day
284 | Numeric::WeekFromSun
285 | Numeric::WeekFromMon
286 | Numeric::IsoWeek
287 | Numeric::Hour
288 | Numeric::Hour12
289 | Numeric::Minute
290 | Numeric::Second => mask.push_str("00"),
291 Numeric::NumDaysFromSun | Numeric::WeekdayFromMon => mask.push('0'),
292 Numeric::Ordinal => mask.push_str("000"),
293 Numeric::Nanosecond => mask.push_str("000000000"),
294 Numeric::Timestamp => mask.push_str("#0000000000"),
295 _ => return Err(fmt::Error),
296 },
297 Item::Fixed(v) => match v {
298 Fixed::ShortMonthName => mask.push_str("___"),
299 Fixed::LongMonthName => mask.push_str("_________"),
300 Fixed::ShortWeekdayName => mask.push_str("___"),
301 Fixed::LongWeekdayName => mask.push_str("________"),
302 Fixed::LowerAmPm => mask.push_str("__"),
303 Fixed::UpperAmPm => mask.push_str("__"),
304 Fixed::Nanosecond => mask.push_str(".#########"),
305 Fixed::Nanosecond3 => mask.push_str(".###"),
306 Fixed::Nanosecond6 => mask.push_str(".######"),
307 Fixed::Nanosecond9 => mask.push_str(".#########"),
308 Fixed::TimezoneName => mask.push_str("__________"),
309 Fixed::TimezoneOffsetColon | Fixed::TimezoneOffset => mask.push_str("+##:##"),
310 Fixed::TimezoneOffsetDoubleColon => mask.push_str("+##:##:##"),
311 Fixed::TimezoneOffsetTripleColon => mask.push_str("+##"),
312 Fixed::TimezoneOffsetColonZ | Fixed::TimezoneOffsetZ => return Err(fmt::Error),
313 Fixed::RFC2822 => {
314 return Err(fmt::Error);
316 }
317 Fixed::RFC3339 => {
318 return Err(fmt::Error);
320 }
321 _ => return Err(fmt::Error),
322 },
323 Item::Error => return Err(fmt::Error),
324 }
325 }
326
327 self.locale = locale;
328 self.pattern = pattern.as_ref().to_string();
329 self.widget.set_mask(mask)?;
330 Ok(())
331 }
332
333 #[inline]
335 pub fn set_invalid(&mut self, invalid: bool) {
336 self.widget.invalid = invalid;
337 }
338
339 #[inline]
341 pub fn get_invalid(&self) -> bool {
342 self.widget.invalid
343 }
344
345 #[inline]
349 pub fn set_overwrite(&mut self, overwrite: bool) {
350 self.widget.set_overwrite(overwrite);
351 }
352
353 #[inline]
355 pub fn overwrite(&self) -> bool {
356 self.widget.overwrite()
357 }
358}
359
360impl DateInputState {
361 #[inline]
364 pub fn set_clipboard(&mut self, clip: Option<impl Clipboard + 'static>) {
365 self.widget.set_clipboard(clip);
366 }
367
368 #[inline]
371 pub fn clipboard(&self) -> Option<&dyn Clipboard> {
372 self.widget.clipboard()
373 }
374
375 #[inline]
377 pub fn copy_to_clip(&mut self) -> bool {
378 self.widget.copy_to_clip()
379 }
380
381 #[inline]
383 pub fn cut_to_clip(&mut self) -> bool {
384 self.widget.cut_to_clip()
385 }
386
387 #[inline]
389 pub fn paste_from_clip(&mut self) -> bool {
390 self.widget.paste_from_clip()
391 }
392}
393
394impl DateInputState {
395 #[inline]
397 pub fn set_undo_buffer(&mut self, undo: Option<impl UndoBuffer + 'static>) {
398 self.widget.set_undo_buffer(undo);
399 }
400
401 #[inline]
403 pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
404 self.widget.undo_buffer()
405 }
406
407 #[inline]
409 pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
410 self.widget.undo_buffer_mut()
411 }
412
413 #[inline]
415 pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
416 self.widget.recent_replay_log()
417 }
418
419 #[inline]
421 pub fn replay_log(&mut self, replay: &[UndoEntry]) {
422 self.widget.replay_log(replay)
423 }
424
425 #[inline]
427 pub fn undo(&mut self) -> bool {
428 self.widget.undo()
429 }
430
431 #[inline]
433 pub fn redo(&mut self) -> bool {
434 self.widget.redo()
435 }
436}
437
438impl DateInputState {
439 #[inline]
441 pub fn set_styles(&mut self, styles: Vec<(Range<usize>, usize)>) {
442 self.widget.set_styles(styles);
443 }
444
445 #[inline]
447 pub fn add_style(&mut self, range: Range<usize>, style: usize) {
448 self.widget.add_style(range, style);
449 }
450
451 #[inline]
454 pub fn add_range_style(
455 &mut self,
456 range: Range<upos_type>,
457 style: usize,
458 ) -> Result<(), TextError> {
459 self.widget.add_range_style(range, style)
460 }
461
462 #[inline]
464 pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
465 self.widget.remove_style(range, style);
466 }
467
468 #[inline]
470 pub fn remove_range_style(
471 &mut self,
472 range: Range<upos_type>,
473 style: usize,
474 ) -> Result<(), TextError> {
475 self.widget.remove_range_style(range, style)
476 }
477
478 pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
480 self.widget.styles_in(range, buf)
481 }
482
483 #[inline]
485 pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
486 self.widget.styles_at(byte_pos, buf)
487 }
488
489 #[inline]
492 pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
493 self.widget.styles_at_match(byte_pos, style)
494 }
495
496 #[inline]
498 pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
499 self.widget.styles()
500 }
501}
502
503impl DateInputState {
504 #[inline]
506 pub fn offset(&self) -> upos_type {
507 self.widget.offset()
508 }
509
510 #[inline]
512 pub fn set_offset(&mut self, offset: upos_type) {
513 self.widget.set_offset(offset)
514 }
515
516 #[inline]
518 pub fn cursor(&self) -> upos_type {
519 self.widget.cursor()
520 }
521
522 #[inline]
524 pub fn set_cursor(&mut self, cursor: upos_type, extend_selection: bool) -> bool {
525 self.widget.set_cursor(cursor, extend_selection)
526 }
527
528 #[inline]
530 pub fn set_default_cursor(&mut self) {
531 self.widget.set_default_cursor()
532 }
533
534 #[inline]
536 pub fn anchor(&self) -> upos_type {
537 self.widget.anchor()
538 }
539
540 #[inline]
542 pub fn has_selection(&self) -> bool {
543 self.widget.has_selection()
544 }
545
546 #[inline]
548 pub fn selection(&self) -> Range<upos_type> {
549 self.widget.selection()
550 }
551
552 #[inline]
554 pub fn set_selection(&mut self, anchor: upos_type, cursor: upos_type) -> bool {
555 self.widget.set_selection(anchor, cursor)
556 }
557
558 #[inline]
560 pub fn select_all(&mut self) {
561 self.widget.select_all();
562 }
563
564 #[inline]
566 pub fn selected_text(&self) -> &str {
567 self.widget.selected_text()
568 }
569}
570
571impl DateInputState {
572 #[inline]
574 pub fn is_empty(&self) -> bool {
575 self.widget.is_empty()
576 }
577
578 #[inline]
580 pub fn value(&self) -> Result<NaiveDate, chrono::ParseError> {
581 NaiveDate::parse_from_str(self.widget.text(), self.pattern.as_str())
582 }
583
584 #[inline]
586 pub fn len(&self) -> upos_type {
587 self.widget.len()
588 }
589
590 #[inline]
592 pub fn line_width(&self) -> upos_type {
593 self.widget.line_width()
594 }
595}
596
597impl DateInputState {
598 #[inline]
600 pub fn clear(&mut self) {
601 self.widget.clear();
602 }
603
604 #[inline]
606 pub fn set_value(&mut self, date: NaiveDate) {
607 let v = date.format(self.pattern.as_str()).to_string();
608 self.widget.set_text(v);
609 }
610
611 #[inline]
613 pub fn insert_char(&mut self, c: char) -> bool {
614 self.widget.insert_char(c)
615 }
616
617 #[inline]
620 pub fn delete_range(&mut self, range: Range<upos_type>) -> bool {
621 self.widget.delete_range(range)
622 }
623
624 #[inline]
627 pub fn try_delete_range(&mut self, range: Range<upos_type>) -> Result<bool, TextError> {
628 self.widget.try_delete_range(range)
629 }
630}
631
632impl DateInputState {
633 #[inline]
635 pub fn delete_next_char(&mut self) -> bool {
636 self.widget.delete_next_char()
637 }
638
639 #[inline]
641 pub fn delete_prev_char(&mut self) -> bool {
642 self.widget.delete_prev_char()
643 }
644
645 #[inline]
647 pub fn move_right(&mut self, extend_selection: bool) -> bool {
648 self.widget.move_right(extend_selection)
649 }
650
651 #[inline]
653 pub fn move_left(&mut self, extend_selection: bool) -> bool {
654 self.widget.move_left(extend_selection)
655 }
656
657 #[inline]
659 pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
660 self.widget.move_to_line_start(extend_selection)
661 }
662
663 #[inline]
665 pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
666 self.widget.move_to_line_end(extend_selection)
667 }
668}
669
670impl HasScreenCursor for DateInputState {
671 #[inline]
673 fn screen_cursor(&self) -> Option<(u16, u16)> {
674 self.widget.screen_cursor()
675 }
676}
677
678impl RelocatableState for DateInputState {
679 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
680 self.widget.relocate(shift, clip);
681 }
682}
683
684impl DateInputState {
685 #[inline]
688 pub fn col_to_screen(&self, pos: upos_type) -> Option<u16> {
689 self.widget.col_to_screen(pos)
690 }
691
692 #[inline]
695 pub fn screen_to_col(&self, scx: i16) -> upos_type {
696 self.widget.screen_to_col(scx)
697 }
698
699 #[inline]
703 pub fn set_screen_cursor(&mut self, cursor: i16, extend_selection: bool) -> bool {
704 self.widget.set_screen_cursor(cursor, extend_selection)
705 }
706}
707
708impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for DateInputState {
709 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
710 self.widget.handle(event, Regular)
711 }
712}
713
714impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for DateInputState {
715 fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
716 self.widget.handle(event, ReadOnly)
717 }
718}
719
720impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for DateInputState {
721 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
722 self.widget.handle(event, MouseOnly)
723 }
724}
725
726pub fn handle_events(
730 state: &mut DateInputState,
731 focus: bool,
732 event: &crossterm::event::Event,
733) -> TextOutcome {
734 state.widget.focus.set(focus);
735 HandleEvent::handle(state, event, Regular)
736}
737
738pub fn handle_readonly_events(
742 state: &mut DateInputState,
743 focus: bool,
744 event: &crossterm::event::Event,
745) -> TextOutcome {
746 state.widget.focus.set(focus);
747 state.handle(event, ReadOnly)
748}
749
750pub fn handle_mouse_events(
752 state: &mut DateInputState,
753 event: &crossterm::event::Event,
754) -> TextOutcome {
755 HandleEvent::handle(state, event, MouseOnly)
756}