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 format_num_pattern::{NumberFmtError, NumberFormat, NumberSymbols};
14use rat_event::{HandleEvent, MouseOnly, Regular};
15use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
16use rat_reloc::RelocatableState;
17use ratatui::buffer::Buffer;
18use ratatui::layout::Rect;
19use ratatui::style::Style;
20use ratatui::widgets::Block;
21use ratatui::widgets::StatefulWidget;
22use std::fmt::{Debug, Display, LowerExp};
23use std::ops::Range;
24use std::str::FromStr;
25
26#[derive(Debug, Default, Clone)]
35pub struct NumberInput<'a> {
36 widget: MaskedInput<'a>,
37}
38
39#[derive(Debug, Clone)]
41pub struct NumberInputState {
42 pub area: Rect,
45 pub inner: Rect,
48
49 pub widget: MaskedInputState,
50
51 pattern: String,
53 locale: format_num_pattern::Locale,
55 format: NumberFormat,
59
60 pub non_exhaustive: NonExhaustive,
61}
62
63impl<'a> NumberInput<'a> {
64 pub fn new() -> Self {
65 Self::default()
66 }
67
68 #[inline]
70 pub fn compact(mut self, compact: bool) -> Self {
71 self.widget = self.widget.compact(compact);
72 self
73 }
74
75 #[inline]
77 pub fn styles(mut self, style: TextStyle) -> Self {
78 self.widget = self.widget.styles(style);
79 self
80 }
81
82 #[inline]
84 pub fn style(mut self, style: impl Into<Style>) -> Self {
85 self.widget = self.widget.style(style);
86 self
87 }
88
89 #[inline]
91 pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
92 self.widget = self.widget.focus_style(style);
93 self
94 }
95
96 #[inline]
98 pub fn select_style(mut self, style: impl Into<Style>) -> Self {
99 self.widget = self.widget.select_style(style);
100 self
101 }
102
103 #[inline]
105 pub fn invalid_style(mut self, style: impl Into<Style>) -> Self {
106 self.widget = self.widget.invalid_style(style);
107 self
108 }
109
110 #[inline]
111 pub fn block(mut self, block: Block<'a>) -> Self {
112 self.widget = self.widget.block(block);
113 self
114 }
115
116 #[inline]
118 pub fn on_focus_gained(mut self, of: TextFocusGained) -> Self {
119 self.widget = self.widget.on_focus_gained(of);
120 self
121 }
122
123 #[inline]
125 pub fn on_focus_lost(mut self, of: TextFocusLost) -> Self {
126 self.widget = self.widget.on_focus_lost(of);
127 self
128 }
129
130 #[inline]
132 pub fn on_tab(mut self, of: TextTab) -> Self {
133 self.widget = self.widget.on_tab(of);
134 self
135 }
136
137 pub fn width(&self, state: &NumberInputState) -> u16 {
139 state.widget.mask.len() as u16 + 1
140 }
141
142 pub fn height(&self) -> u16 {
144 1
145 }
146}
147
148impl<'a> StatefulWidget for &NumberInput<'a> {
149 type State = NumberInputState;
150
151 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
152 (&self.widget).render(area, buf, &mut state.widget);
153
154 state.area = state.widget.area;
155 state.inner = state.widget.inner;
156 }
157}
158
159impl StatefulWidget for NumberInput<'_> {
160 type State = NumberInputState;
161
162 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
163 self.widget.render(area, buf, &mut state.widget);
164
165 state.area = state.widget.area;
166 state.inner = state.widget.inner;
167 }
168}
169
170impl Default for NumberInputState {
171 fn default() -> Self {
172 let mut s = Self {
173 area: Default::default(),
174 inner: Default::default(),
175 widget: Default::default(),
176 pattern: "".to_string(),
177 locale: Default::default(),
178 format: Default::default(),
179 non_exhaustive: NonExhaustive,
180 };
181 _ = s.set_format("#####");
182 s
183 }
184}
185
186impl HasFocus for NumberInputState {
187 fn build(&self, builder: &mut FocusBuilder) {
188 builder.leaf_widget(self);
189 }
190
191 #[inline]
192 fn focus(&self) -> FocusFlag {
193 self.widget.focus.clone()
194 }
195
196 #[inline]
197 fn area(&self) -> Rect {
198 self.widget.area
199 }
200
201 fn navigable(&self) -> Navigation {
202 self.widget.navigable()
203 }
204}
205
206impl NumberInputState {
207 pub fn new() -> Self {
208 Self::default()
209 }
210
211 pub fn new_pattern<S: AsRef<str>>(pattern: S) -> Result<Self, NumberFmtError> {
212 let mut s = Self::default();
213 s.set_format(pattern)?;
214 Ok(s)
215 }
216
217 pub fn new_loc_pattern<S: AsRef<str>>(
218 pattern: S,
219 locale: format_num_pattern::Locale,
220 ) -> Result<Self, NumberFmtError> {
221 let mut s = Self::default();
222 s.set_format_loc(pattern.as_ref(), locale)?;
223 Ok(s)
224 }
225
226 pub fn named(name: &str) -> Self {
227 let mut z = Self::default();
228 z.widget.focus = z.widget.focus.with_name(name);
229 z
230 }
231
232 pub fn with_pattern<S: AsRef<str>>(mut self, pattern: S) -> Result<Self, NumberFmtError> {
233 self.set_format(pattern)?;
234 Ok(self)
235 }
236
237 pub fn with_loc_pattern<S: AsRef<str>>(
238 mut self,
239 pattern: S,
240 locale: format_num_pattern::Locale,
241 ) -> Result<Self, NumberFmtError> {
242 self.set_format_loc(pattern.as_ref(), locale)?;
243 Ok(self)
244 }
245
246 #[inline]
248 pub fn format(&self) -> &str {
249 self.pattern.as_str()
250 }
251
252 #[inline]
254 pub fn locale(&self) -> chrono::Locale {
255 self.locale
256 }
257
258 pub fn set_format<S: AsRef<str>>(&mut self, pattern: S) -> Result<(), NumberFmtError> {
260 self.set_format_loc(pattern, format_num_pattern::Locale::default())
261 }
262
263 pub fn set_format_loc<S: AsRef<str>>(
265 &mut self,
266 pattern: S,
267 locale: format_num_pattern::Locale,
268 ) -> Result<(), NumberFmtError> {
269 let sym = NumberSymbols::monetary(locale);
270
271 self.format = NumberFormat::new(pattern.as_ref())?;
272 self.widget.set_mask(pattern.as_ref())?;
273 self.widget.set_num_symbols(sym);
274
275 Ok(())
276 }
277
278 #[inline]
280 pub fn set_invalid(&mut self, invalid: bool) {
281 self.widget.invalid = invalid;
282 }
283
284 #[inline]
286 pub fn get_invalid(&self) -> bool {
287 self.widget.invalid
288 }
289
290 #[inline]
294 pub fn set_overwrite(&mut self, overwrite: bool) {
295 self.widget.set_overwrite(overwrite);
296 }
297
298 #[inline]
300 pub fn overwrite(&self) -> bool {
301 self.widget.overwrite()
302 }
303}
304
305impl NumberInputState {
306 #[inline]
309 pub fn set_clipboard(&mut self, clip: Option<impl Clipboard + 'static>) {
310 self.widget.set_clipboard(clip);
311 }
312
313 #[inline]
316 pub fn clipboard(&self) -> Option<&dyn Clipboard> {
317 self.widget.clipboard()
318 }
319
320 #[inline]
322 pub fn copy_to_clip(&mut self) -> bool {
323 self.widget.copy_to_clip()
324 }
325
326 #[inline]
328 pub fn cut_to_clip(&mut self) -> bool {
329 self.widget.cut_to_clip()
330 }
331
332 #[inline]
334 pub fn paste_from_clip(&mut self) -> bool {
335 self.widget.paste_from_clip()
336 }
337}
338
339impl NumberInputState {
340 #[inline]
342 pub fn set_undo_buffer(&mut self, undo: Option<impl UndoBuffer + 'static>) {
343 self.widget.set_undo_buffer(undo);
344 }
345
346 #[inline]
348 pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
349 self.widget.undo_buffer()
350 }
351
352 #[inline]
354 pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
355 self.widget.undo_buffer_mut()
356 }
357
358 #[inline]
360 pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
361 self.widget.recent_replay_log()
362 }
363
364 #[inline]
366 pub fn replay_log(&mut self, replay: &[UndoEntry]) {
367 self.widget.replay_log(replay)
368 }
369
370 #[inline]
372 pub fn undo(&mut self) -> bool {
373 self.widget.undo()
374 }
375
376 #[inline]
378 pub fn redo(&mut self) -> bool {
379 self.widget.redo()
380 }
381}
382
383impl NumberInputState {
384 #[inline]
386 pub fn set_styles(&mut self, styles: Vec<(Range<usize>, usize)>) {
387 self.widget.set_styles(styles);
388 }
389
390 #[inline]
393 pub fn add_style(&mut self, range: Range<usize>, style: usize) {
394 self.widget.add_style(range, style);
395 }
396
397 #[inline]
400 pub fn add_range_style(
401 &mut self,
402 range: Range<upos_type>,
403 style: usize,
404 ) -> Result<(), TextError> {
405 self.widget.add_range_style(range, style)
406 }
407
408 #[inline]
410 pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
411 self.widget.remove_style(range, style);
412 }
413
414 #[inline]
416 pub fn remove_range_style(
417 &mut self,
418 range: Range<upos_type>,
419 style: usize,
420 ) -> Result<(), TextError> {
421 self.widget.remove_range_style(range, style)
422 }
423
424 pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
426 self.widget.styles_in(range, buf)
427 }
428
429 #[inline]
431 pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
432 self.widget.styles_at(byte_pos, buf)
433 }
434
435 #[inline]
438 pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
439 self.widget.styles_at_match(byte_pos, style)
440 }
441
442 #[inline]
444 pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
445 self.widget.styles()
446 }
447}
448
449impl NumberInputState {
450 #[inline]
452 pub fn offset(&self) -> upos_type {
453 self.widget.offset()
454 }
455
456 #[inline]
458 pub fn set_offset(&mut self, offset: upos_type) {
459 self.widget.set_offset(offset)
460 }
461
462 #[inline]
464 pub fn cursor(&self) -> upos_type {
465 self.widget.cursor()
466 }
467
468 #[inline]
470 pub fn set_cursor(&mut self, cursor: upos_type, extend_selection: bool) -> bool {
471 self.widget.set_cursor(cursor, extend_selection)
472 }
473
474 #[inline]
476 pub fn set_default_cursor(&mut self) {
477 self.widget.set_default_cursor()
478 }
479
480 #[inline]
482 pub fn anchor(&self) -> upos_type {
483 self.widget.anchor()
484 }
485
486 #[inline]
488 pub fn has_selection(&self) -> bool {
489 self.widget.has_selection()
490 }
491
492 #[inline]
494 pub fn selection(&self) -> Range<upos_type> {
495 self.widget.selection()
496 }
497
498 #[inline]
500 pub fn set_selection(&mut self, anchor: upos_type, cursor: upos_type) -> bool {
501 self.widget.set_selection(anchor, cursor)
502 }
503
504 #[inline]
506 pub fn select_all(&mut self) {
507 self.widget.select_all();
508 }
509
510 #[inline]
512 pub fn selected_text(&self) -> &str {
513 self.widget.selected_text()
514 }
515}
516
517impl NumberInputState {
518 #[inline]
520 pub fn is_empty(&self) -> bool {
521 self.widget.is_empty()
522 }
523
524 pub fn value_opt<T: FromStr>(&self) -> Result<Option<T>, NumberFmtError> {
527 let s = self.widget.text();
528 if s.trim().is_empty() {
529 Ok(None)
530 } else {
531 self.format.parse(s).map(|v| Some(v))
532 }
533 }
534
535 pub fn value<T: FromStr>(&self) -> Result<T, NumberFmtError> {
537 let s = self.widget.text();
538 self.format.parse(s)
539 }
540
541 #[inline]
543 pub fn len(&self) -> upos_type {
544 self.widget.len()
545 }
546
547 #[inline]
549 pub fn line_width(&self) -> upos_type {
550 self.widget.line_width()
551 }
552}
553
554impl NumberInputState {
555 #[inline]
557 pub fn clear(&mut self) {
558 self.widget.clear();
559 }
560
561 pub fn set_value<T: LowerExp + Display + Debug>(
563 &mut self,
564 number: T,
565 ) -> Result<(), NumberFmtError> {
566 let s = self.format.fmt(number)?;
567 self.widget.set_text(s);
568 Ok(())
569 }
570
571 #[inline]
573 pub fn insert_char(&mut self, c: char) -> bool {
574 self.widget.insert_char(c)
575 }
576
577 #[inline]
580 pub fn delete_range(&mut self, range: Range<upos_type>) -> bool {
581 self.widget.delete_range(range)
582 }
583
584 #[inline]
587 pub fn try_delete_range(&mut self, range: Range<upos_type>) -> Result<bool, TextError> {
588 self.widget.try_delete_range(range)
589 }
590}
591
592impl NumberInputState {
593 #[inline]
595 pub fn delete_next_char(&mut self) -> bool {
596 self.widget.delete_next_char()
597 }
598
599 #[inline]
601 pub fn delete_prev_char(&mut self) -> bool {
602 self.widget.delete_prev_char()
603 }
604
605 #[inline]
607 pub fn move_right(&mut self, extend_selection: bool) -> bool {
608 self.widget.move_right(extend_selection)
609 }
610
611 #[inline]
613 pub fn move_left(&mut self, extend_selection: bool) -> bool {
614 self.widget.move_left(extend_selection)
615 }
616
617 #[inline]
619 pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
620 self.widget.move_to_line_start(extend_selection)
621 }
622
623 #[inline]
625 pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
626 self.widget.move_to_line_end(extend_selection)
627 }
628}
629
630impl HasScreenCursor for NumberInputState {
631 #[inline]
633 fn screen_cursor(&self) -> Option<(u16, u16)> {
634 self.widget.screen_cursor()
635 }
636}
637
638impl RelocatableState for NumberInputState {
639 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
640 self.area.relocate(shift, clip);
641 self.inner.relocate(shift, clip);
642 self.widget.relocate(shift, clip);
643 }
644}
645
646impl NumberInputState {
647 #[inline]
650 pub fn col_to_screen(&self, pos: upos_type) -> Option<u16> {
651 self.widget.col_to_screen(pos)
652 }
653
654 #[inline]
657 pub fn screen_to_col(&self, scx: i16) -> upos_type {
658 self.widget.screen_to_col(scx)
659 }
660
661 #[inline]
665 pub fn set_screen_cursor(&mut self, cursor: i16, extend_selection: bool) -> bool {
666 self.widget.set_screen_cursor(cursor, extend_selection)
667 }
668}
669
670impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for NumberInputState {
671 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
672 self.widget.handle(event, Regular)
673 }
674}
675
676impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for NumberInputState {
677 fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
678 self.widget.handle(event, ReadOnly)
679 }
680}
681
682impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for NumberInputState {
683 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
684 self.widget.handle(event, MouseOnly)
685 }
686}
687
688pub fn handle_events(
692 state: &mut NumberInputState,
693 focus: bool,
694 event: &crossterm::event::Event,
695) -> TextOutcome {
696 state.widget.focus.set(focus);
697 HandleEvent::handle(state, event, Regular)
698}
699
700pub fn handle_readonly_events(
704 state: &mut NumberInputState,
705 focus: bool,
706 event: &crossterm::event::Event,
707) -> TextOutcome {
708 state.widget.focus.set(focus);
709 state.handle(event, ReadOnly)
710}
711
712pub fn handle_mouse_events(
714 state: &mut NumberInputState,
715 event: &crossterm::event::Event,
716) -> TextOutcome {
717 HandleEvent::handle(state, event, MouseOnly)
718}