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