1use crate::_private::NonExhaustive;
6use crate::event::{ReadOnly, TextOutcome};
7use crate::text_input_mask::{MaskedInput, MaskedInputState};
8use crate::{
9 TextFocusGained, TextFocusLost, TextStyle, TextTab, derive_text_widget,
10 derive_text_widget_state,
11};
12use chrono::NaiveDate;
13use chrono::format::{Fixed, Item, Numeric, Pad, StrftimeItems};
14use rat_event::{HandleEvent, MouseOnly, Regular};
15use ratatui::buffer::Buffer;
16use ratatui::layout::Rect;
17use ratatui::style::Style;
18use ratatui::widgets::Block;
19use ratatui::widgets::StatefulWidget;
20use std::fmt;
21use unicode_segmentation::UnicodeSegmentation;
22
23#[derive(Debug, Default, Clone)]
29pub struct DateInput<'a> {
30 widget: MaskedInput<'a>,
31}
32
33#[derive(Debug, Clone)]
36pub struct DateInputState {
37 pub area: Rect,
40 pub inner: Rect,
43 pub widget: MaskedInputState,
45 pattern: String,
47 locale: chrono::Locale,
49
50 pub non_exhaustive: NonExhaustive,
51}
52
53derive_text_widget!(DateInput<'a>);
54
55impl<'a> DateInput<'a> {
56 pub fn new() -> Self {
57 Self::default()
58 }
59
60 #[inline]
62 pub fn compact(mut self, compact: bool) -> Self {
63 self.widget = self.widget.compact(compact);
64 self
65 }
66
67 pub fn width(&self, state: &DateInputState) -> u16 {
69 state.widget.mask.len() as u16 + 1
70 }
71
72 pub fn height(&self) -> u16 {
74 1
75 }
76}
77
78impl<'a> StatefulWidget for &DateInput<'a> {
79 type State = DateInputState;
80
81 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
82 (&self.widget).render(area, buf, &mut state.widget);
83
84 state.area = state.widget.area;
85 state.inner = state.widget.inner;
86 }
87}
88
89impl StatefulWidget for DateInput<'_> {
90 type State = DateInputState;
91
92 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
93 self.widget.render(area, buf, &mut state.widget);
94
95 state.area = state.widget.area;
96 state.inner = state.widget.inner;
97 }
98}
99
100derive_text_widget_state!(DateInputState);
101
102impl Default for DateInputState {
103 fn default() -> Self {
104 Self {
105 area: Default::default(),
106 inner: Default::default(),
107 widget: Default::default(),
108 pattern: Default::default(),
109 locale: Default::default(),
110 non_exhaustive: NonExhaustive,
111 }
112 }
113}
114
115impl DateInputState {
116 pub fn new() -> Self {
118 Self::default()
119 }
120
121 pub fn named(name: &str) -> Self {
122 let mut z = Self::default();
123 z.widget.focus = z.widget.focus.with_name(name);
124 z
125 }
126
127 pub fn with_pattern<S: AsRef<str>>(mut self, pattern: S) -> Result<Self, fmt::Error> {
129 self.set_format(pattern)?;
130 Ok(self)
131 }
132
133 #[inline]
135 pub fn with_loc_pattern<S: AsRef<str>>(
136 mut self,
137 pattern: S,
138 locale: chrono::Locale,
139 ) -> Result<Self, fmt::Error> {
140 self.set_format_loc(pattern, locale)?;
141 Ok(self)
142 }
143
144 #[inline]
146 pub fn format(&self) -> &str {
147 self.pattern.as_str()
148 }
149
150 #[inline]
152 pub fn locale(&self) -> chrono::Locale {
153 self.locale
154 }
155
156 #[inline]
161 pub fn set_format<S: AsRef<str>>(&mut self, pattern: S) -> Result<(), fmt::Error> {
162 self.set_format_loc(pattern, chrono::Locale::default())
163 }
164
165 #[inline]
170 pub fn set_format_loc<S: AsRef<str>>(
171 &mut self,
172 pattern: S,
173 locale: chrono::Locale,
174 ) -> Result<(), fmt::Error> {
175 let mut mask = String::new();
176 let items = StrftimeItems::new_with_locale(pattern.as_ref(), locale)
177 .parse()
178 .map_err(|_| fmt::Error)?;
179 for t in &items {
180 match t {
181 Item::Literal(s) => {
182 for c in s.graphemes(true) {
183 mask.push('\\');
184 mask.push_str(c);
185 }
186 }
187 Item::OwnedLiteral(s) => {
188 for c in s.graphemes(true) {
189 mask.push('\\');
190 mask.push_str(c);
191 }
192 }
193 Item::Space(s) => {
194 for c in s.graphemes(true) {
195 mask.push_str(c);
196 }
197 }
198 Item::OwnedSpace(s) => {
199 for c in s.graphemes(true) {
200 mask.push_str(c);
201 }
202 }
203 Item::Numeric(v, Pad::None | Pad::Space) => match v {
204 Numeric::Year | Numeric::IsoYear => mask.push_str("9999"),
205 Numeric::YearDiv100
206 | Numeric::YearMod100
207 | Numeric::IsoYearDiv100
208 | Numeric::IsoYearMod100
209 | Numeric::Month
210 | Numeric::Day
211 | Numeric::WeekFromSun
212 | Numeric::WeekFromMon
213 | Numeric::IsoWeek
214 | Numeric::Hour
215 | Numeric::Hour12
216 | Numeric::Minute
217 | Numeric::Second => mask.push_str("99"),
218 Numeric::NumDaysFromSun | Numeric::WeekdayFromMon => mask.push('9'),
219 Numeric::Ordinal => mask.push_str("999"),
220 Numeric::Nanosecond => mask.push_str("999999999"),
221 Numeric::Timestamp => mask.push_str("###########"),
222 _ => return Err(fmt::Error),
223 },
224 Item::Numeric(v, Pad::Zero) => match v {
225 Numeric::Year | Numeric::IsoYear => mask.push_str("0000"),
226 Numeric::YearDiv100
227 | Numeric::YearMod100
228 | Numeric::IsoYearDiv100
229 | Numeric::IsoYearMod100
230 | Numeric::Month
231 | Numeric::Day
232 | Numeric::WeekFromSun
233 | Numeric::WeekFromMon
234 | Numeric::IsoWeek
235 | Numeric::Hour
236 | Numeric::Hour12
237 | Numeric::Minute
238 | Numeric::Second => mask.push_str("00"),
239 Numeric::NumDaysFromSun | Numeric::WeekdayFromMon => mask.push('0'),
240 Numeric::Ordinal => mask.push_str("000"),
241 Numeric::Nanosecond => mask.push_str("000000000"),
242 Numeric::Timestamp => mask.push_str("#0000000000"),
243 _ => return Err(fmt::Error),
244 },
245 Item::Fixed(v) => match v {
246 Fixed::ShortMonthName => mask.push_str("___"),
247 Fixed::LongMonthName => mask.push_str("_________"),
248 Fixed::ShortWeekdayName => mask.push_str("___"),
249 Fixed::LongWeekdayName => mask.push_str("________"),
250 Fixed::LowerAmPm => mask.push_str("__"),
251 Fixed::UpperAmPm => mask.push_str("__"),
252 Fixed::Nanosecond => mask.push_str(".#########"),
253 Fixed::Nanosecond3 => mask.push_str(".###"),
254 Fixed::Nanosecond6 => mask.push_str(".######"),
255 Fixed::Nanosecond9 => mask.push_str(".#########"),
256 Fixed::TimezoneName => mask.push_str("__________"),
257 Fixed::TimezoneOffsetColon | Fixed::TimezoneOffset => mask.push_str("+##:##"),
258 Fixed::TimezoneOffsetDoubleColon => mask.push_str("+##:##:##"),
259 Fixed::TimezoneOffsetTripleColon => mask.push_str("+##"),
260 Fixed::TimezoneOffsetColonZ | Fixed::TimezoneOffsetZ => return Err(fmt::Error),
261 Fixed::RFC2822 => {
262 return Err(fmt::Error);
264 }
265 Fixed::RFC3339 => {
266 return Err(fmt::Error);
268 }
269 _ => return Err(fmt::Error),
270 },
271 Item::Error => return Err(fmt::Error),
272 }
273 }
274
275 self.locale = locale;
276 self.pattern = pattern.as_ref().to_string();
277 self.widget.set_mask(mask)?;
278 Ok(())
279 }
280}
281
282impl DateInputState {
283 #[inline]
285 pub fn clear(&mut self) {
286 self.widget.clear();
287 }
288
289 #[inline]
291 pub fn set_value(&mut self, date: NaiveDate) {
292 let v = date.format(self.pattern.as_str()).to_string();
293 self.widget.set_text(v);
294 }
295
296 #[inline]
298 pub fn value(&self) -> Result<NaiveDate, chrono::ParseError> {
299 NaiveDate::parse_from_str(self.widget.text(), self.pattern.as_str())
300 }
301}
302
303impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for DateInputState {
304 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
305 self.widget.handle(event, Regular)
306 }
307}
308
309impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for DateInputState {
310 fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
311 self.widget.handle(event, ReadOnly)
312 }
313}
314
315impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for DateInputState {
316 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
317 self.widget.handle(event, MouseOnly)
318 }
319}
320
321pub fn handle_events(
325 state: &mut DateInputState,
326 focus: bool,
327 event: &crossterm::event::Event,
328) -> TextOutcome {
329 state.widget.focus.set(focus);
330 HandleEvent::handle(state, event, Regular)
331}
332
333pub fn handle_readonly_events(
337 state: &mut DateInputState,
338 focus: bool,
339 event: &crossterm::event::Event,
340) -> TextOutcome {
341 state.widget.focus.set(focus);
342 state.handle(event, ReadOnly)
343}
344
345pub fn handle_mouse_events(
347 state: &mut DateInputState,
348 event: &crossterm::event::Event,
349) -> TextOutcome {
350 HandleEvent::handle(state, event, MouseOnly)
351}