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