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 format_num_pattern::{NumberFmtError, NumberFormat, NumberSymbols};
12use rat_event::{HandleEvent, MouseOnly, Regular};
13use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
14use rat_reloc::RelocatableState;
15use ratatui::buffer::Buffer;
16use ratatui::layout::Rect;
17use ratatui::prelude::{StatefulWidget, Style};
18use ratatui::widgets::Block;
19#[cfg(feature = "unstable-widget-ref")]
20use ratatui::widgets::StatefulWidgetRef;
21use std::fmt::{Debug, Display, LowerExp};
22use std::ops::Range;
23use std::str::FromStr;
24
25#[derive(Debug, Default, Clone)]
34pub struct NumberInput<'a> {
35 widget: MaskedInput<'a>,
36}
37
38#[derive(Debug, Clone)]
40pub struct NumberInputState {
41 pub widget: MaskedInputState,
42
43 pattern: String,
45 locale: format_num_pattern::Locale,
47 format: NumberFormat,
51
52 pub non_exhaustive: NonExhaustive,
53}
54
55impl<'a> NumberInput<'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 #[inline]
69 pub fn styles(mut self, style: TextStyle) -> Self {
70 self.widget = self.widget.styles(style);
71 self
72 }
73
74 #[inline]
76 pub fn style(mut self, style: impl Into<Style>) -> Self {
77 self.widget = self.widget.style(style);
78 self
79 }
80
81 #[inline]
83 pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
84 self.widget = self.widget.focus_style(style);
85 self
86 }
87
88 #[inline]
90 pub fn select_style(mut self, style: impl Into<Style>) -> Self {
91 self.widget = self.widget.select_style(style);
92 self
93 }
94
95 #[inline]
97 pub fn invalid_style(mut self, style: impl Into<Style>) -> Self {
98 self.widget = self.widget.invalid_style(style);
99 self
100 }
101
102 #[inline]
103 pub fn block(mut self, block: Block<'a>) -> Self {
104 self.widget = self.widget.block(block);
105 self
106 }
107
108 #[inline]
110 pub fn on_focus_gained(mut self, of: TextFocusGained) -> Self {
111 self.widget = self.widget.on_focus_gained(of);
112 self
113 }
114
115 #[inline]
117 pub fn on_focus_lost(mut self, of: TextFocusLost) -> Self {
118 self.widget = self.widget.on_focus_lost(of);
119 self
120 }
121}
122
123#[cfg(feature = "unstable-widget-ref")]
124impl<'a> StatefulWidgetRef for NumberInput<'a> {
125 type State = NumberInputState;
126
127 fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
128 self.widget.render_ref(area, buf, &mut state.widget);
129 }
130}
131
132impl StatefulWidget for NumberInput<'_> {
133 type State = NumberInputState;
134
135 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
136 self.widget.render(area, buf, &mut state.widget);
137 }
138}
139
140impl Default for NumberInputState {
141 fn default() -> Self {
142 let mut s = Self {
143 widget: Default::default(),
144 pattern: "".to_string(),
145 locale: Default::default(),
146 format: Default::default(),
147 non_exhaustive: NonExhaustive,
148 };
149 _ = s.set_format("#####");
150 s
151 }
152}
153
154impl HasFocus for NumberInputState {
155 fn build(&self, builder: &mut FocusBuilder) {
156 builder.leaf_widget(self);
157 }
158
159 #[inline]
160 fn focus(&self) -> FocusFlag {
161 self.widget.focus.clone()
162 }
163
164 #[inline]
165 fn area(&self) -> Rect {
166 self.widget.area
167 }
168
169 fn navigable(&self) -> Navigation {
170 self.widget.navigable()
171 }
172}
173
174impl NumberInputState {
175 pub fn new() -> Self {
176 Self::default()
177 }
178
179 pub fn new_pattern<S: AsRef<str>>(pattern: S) -> Result<Self, NumberFmtError> {
180 let mut s = Self::default();
181 s.set_format(pattern)?;
182 Ok(s)
183 }
184
185 pub fn new_loc_pattern<S: AsRef<str>>(
186 pattern: S,
187 locale: format_num_pattern::Locale,
188 ) -> Result<Self, NumberFmtError> {
189 let mut s = Self::default();
190 s.set_format_loc(pattern.as_ref(), locale)?;
191 Ok(s)
192 }
193
194 pub fn named(name: &str) -> Self {
195 Self {
196 widget: MaskedInputState::named(name),
197 ..Default::default()
198 }
199 }
200
201 pub fn with_pattern<S: AsRef<str>>(mut self, pattern: S) -> Result<Self, NumberFmtError> {
202 self.set_format(pattern)?;
203 Ok(self)
204 }
205
206 pub fn with_loc_pattern<S: AsRef<str>>(
207 mut self,
208 pattern: S,
209 locale: format_num_pattern::Locale,
210 ) -> Result<Self, NumberFmtError> {
211 self.set_format_loc(pattern.as_ref(), locale)?;
212 Ok(self)
213 }
214
215 #[inline]
217 pub fn format(&self) -> &str {
218 self.pattern.as_str()
219 }
220
221 #[inline]
223 pub fn locale(&self) -> chrono::Locale {
224 self.locale
225 }
226
227 pub fn set_format<S: AsRef<str>>(&mut self, pattern: S) -> Result<(), NumberFmtError> {
229 self.set_format_loc(pattern, format_num_pattern::Locale::default())
230 }
231
232 pub fn set_format_loc<S: AsRef<str>>(
234 &mut self,
235 pattern: S,
236 locale: format_num_pattern::Locale,
237 ) -> Result<(), NumberFmtError> {
238 let sym = NumberSymbols::monetary(locale);
239
240 self.format = NumberFormat::new(pattern.as_ref())?;
241 self.widget.set_mask(pattern.as_ref())?;
242 self.widget.set_num_symbols(sym);
243
244 Ok(())
245 }
246
247 #[inline]
249 pub fn set_invalid(&mut self, invalid: bool) {
250 self.widget.invalid = invalid;
251 }
252
253 #[inline]
255 pub fn get_invalid(&self) -> bool {
256 self.widget.invalid
257 }
258
259 #[inline]
263 pub fn set_overwrite(&mut self, overwrite: bool) {
264 self.widget.overwrite = overwrite;
265 }
266
267 #[inline]
269 pub fn overwrite(&self) -> bool {
270 self.widget.overwrite
271 }
272}
273
274impl NumberInputState {
275 #[inline]
278 pub fn set_clipboard(&mut self, clip: Option<impl Clipboard + 'static>) {
279 self.widget.set_clipboard(clip);
280 }
281
282 #[inline]
285 pub fn clipboard(&self) -> Option<&dyn Clipboard> {
286 self.widget.clipboard()
287 }
288
289 #[inline]
291 pub fn copy_to_clip(&mut self) -> bool {
292 self.widget.copy_to_clip()
293 }
294
295 #[inline]
297 pub fn cut_to_clip(&mut self) -> bool {
298 self.widget.cut_to_clip()
299 }
300
301 #[inline]
303 pub fn paste_from_clip(&mut self) -> bool {
304 self.widget.paste_from_clip()
305 }
306}
307
308impl NumberInputState {
309 #[inline]
311 pub fn set_undo_buffer(&mut self, undo: Option<impl UndoBuffer + 'static>) {
312 self.widget.set_undo_buffer(undo);
313 }
314
315 #[inline]
317 pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
318 self.widget.undo_buffer()
319 }
320
321 #[inline]
323 pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
324 self.widget.undo_buffer_mut()
325 }
326
327 #[inline]
329 pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
330 self.widget.recent_replay_log()
331 }
332
333 #[inline]
335 pub fn replay_log(&mut self, replay: &[UndoEntry]) {
336 self.widget.replay_log(replay)
337 }
338
339 #[inline]
341 pub fn undo(&mut self) -> bool {
342 self.widget.undo()
343 }
344
345 #[inline]
347 pub fn redo(&mut self) -> bool {
348 self.widget.redo()
349 }
350}
351
352impl NumberInputState {
353 #[inline]
355 pub fn set_styles(&mut self, styles: Vec<(Range<usize>, usize)>) {
356 self.widget.set_styles(styles);
357 }
358
359 #[inline]
362 pub fn add_style(&mut self, range: Range<usize>, style: usize) {
363 self.widget.add_style(range, style);
364 }
365
366 #[inline]
369 pub fn add_range_style(
370 &mut self,
371 range: Range<upos_type>,
372 style: usize,
373 ) -> Result<(), TextError> {
374 self.widget.add_range_style(range, style)
375 }
376
377 #[inline]
379 pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
380 self.widget.remove_style(range, style);
381 }
382
383 #[inline]
385 pub fn remove_range_style(
386 &mut self,
387 range: Range<upos_type>,
388 style: usize,
389 ) -> Result<(), TextError> {
390 self.widget.remove_range_style(range, style)
391 }
392
393 pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
395 self.widget.styles_in(range, buf)
396 }
397
398 #[inline]
400 pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
401 self.widget.styles_at(byte_pos, buf)
402 }
403
404 #[inline]
407 pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
408 self.widget.style_match(byte_pos, style)
409 }
410
411 #[inline]
413 pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
414 self.widget.styles()
415 }
416}
417
418impl NumberInputState {
419 #[inline]
421 pub fn offset(&self) -> upos_type {
422 self.widget.offset()
423 }
424
425 #[inline]
427 pub fn set_offset(&mut self, offset: upos_type) {
428 self.widget.set_offset(offset)
429 }
430
431 #[inline]
433 pub fn cursor(&self) -> upos_type {
434 self.widget.cursor()
435 }
436
437 #[inline]
439 pub fn set_cursor(&mut self, cursor: upos_type, extend_selection: bool) -> bool {
440 self.widget.set_cursor(cursor, extend_selection)
441 }
442
443 #[inline]
445 pub fn set_default_cursor(&mut self) {
446 self.widget.set_default_cursor()
447 }
448
449 #[inline]
451 pub fn anchor(&self) -> upos_type {
452 self.widget.anchor()
453 }
454
455 #[inline]
457 pub fn has_selection(&self) -> bool {
458 self.widget.has_selection()
459 }
460
461 #[inline]
463 pub fn selection(&self) -> Range<upos_type> {
464 self.widget.selection()
465 }
466
467 #[inline]
469 pub fn set_selection(&mut self, anchor: upos_type, cursor: upos_type) -> bool {
470 self.widget.set_selection(anchor, cursor)
471 }
472
473 #[inline]
475 pub fn select_all(&mut self) {
476 self.widget.select_all();
477 }
478
479 #[inline]
481 pub fn selected_text(&self) -> &str {
482 self.widget.selected_text()
483 }
484}
485
486impl NumberInputState {
487 #[inline]
489 pub fn is_empty(&self) -> bool {
490 self.widget.is_empty()
491 }
492
493 pub fn value_opt<T: FromStr>(&self) -> Result<Option<T>, NumberFmtError> {
496 let s = self.widget.text();
497 if s.trim().is_empty() {
498 Ok(None)
499 } else {
500 self.format.parse(s).map(|v| Some(v))
501 }
502 }
503
504 pub fn value<T: FromStr>(&self) -> Result<T, NumberFmtError> {
506 let s = self.widget.text();
507 self.format.parse(s)
508 }
509
510 #[inline]
512 pub fn len(&self) -> upos_type {
513 self.widget.len()
514 }
515
516 #[inline]
518 pub fn line_width(&self) -> upos_type {
519 self.widget.line_width()
520 }
521}
522
523impl NumberInputState {
524 #[inline]
526 pub fn clear(&mut self) {
527 self.widget.clear();
528 }
529
530 pub fn set_value<T: LowerExp + Display + Debug>(
532 &mut self,
533 number: T,
534 ) -> Result<(), NumberFmtError> {
535 let s = self.format.fmt(number)?;
536 self.widget.set_text(s);
537 Ok(())
538 }
539
540 #[inline]
542 pub fn insert_char(&mut self, c: char) -> bool {
543 self.widget.insert_char(c)
544 }
545
546 #[inline]
549 pub fn delete_range(&mut self, range: Range<upos_type>) -> bool {
550 self.widget.delete_range(range)
551 }
552
553 #[inline]
556 pub fn try_delete_range(&mut self, range: Range<upos_type>) -> Result<bool, TextError> {
557 self.widget.try_delete_range(range)
558 }
559}
560
561impl NumberInputState {
562 #[inline]
564 pub fn delete_next_char(&mut self) -> bool {
565 self.widget.delete_next_char()
566 }
567
568 #[inline]
570 pub fn delete_prev_char(&mut self) -> bool {
571 self.widget.delete_prev_char()
572 }
573
574 #[inline]
576 pub fn move_right(&mut self, extend_selection: bool) -> bool {
577 self.widget.move_right(extend_selection)
578 }
579
580 #[inline]
582 pub fn move_left(&mut self, extend_selection: bool) -> bool {
583 self.widget.move_left(extend_selection)
584 }
585
586 #[inline]
588 pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
589 self.widget.move_to_line_start(extend_selection)
590 }
591
592 #[inline]
594 pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
595 self.widget.move_to_line_end(extend_selection)
596 }
597}
598
599impl HasScreenCursor for NumberInputState {
600 #[inline]
602 fn screen_cursor(&self) -> Option<(u16, u16)> {
603 self.widget.screen_cursor()
604 }
605}
606
607impl RelocatableState for NumberInputState {
608 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
609 self.widget.relocate(shift, clip);
610 }
611}
612
613impl NumberInputState {
614 #[inline]
617 pub fn col_to_screen(&self, pos: upos_type) -> Option<u16> {
618 self.widget.col_to_screen(pos)
619 }
620
621 #[inline]
624 pub fn screen_to_col(&self, scx: i16) -> upos_type {
625 self.widget.screen_to_col(scx)
626 }
627
628 #[inline]
632 pub fn set_screen_cursor(&mut self, cursor: i16, extend_selection: bool) -> bool {
633 self.widget.set_screen_cursor(cursor, extend_selection)
634 }
635}
636
637impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for NumberInputState {
638 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
639 self.widget.handle(event, Regular)
640 }
641}
642
643impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for NumberInputState {
644 fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
645 self.widget.handle(event, ReadOnly)
646 }
647}
648
649impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for NumberInputState {
650 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
651 self.widget.handle(event, MouseOnly)
652 }
653}
654
655pub fn handle_events(
659 state: &mut NumberInputState,
660 focus: bool,
661 event: &crossterm::event::Event,
662) -> TextOutcome {
663 state.widget.focus.set(focus);
664 HandleEvent::handle(state, event, Regular)
665}
666
667pub fn handle_readonly_events(
671 state: &mut NumberInputState,
672 focus: bool,
673 event: &crossterm::event::Event,
674) -> TextOutcome {
675 state.widget.focus.set(focus);
676 state.handle(event, ReadOnly)
677}
678
679pub fn handle_mouse_events(
681 state: &mut NumberInputState,
682 event: &crossterm::event::Event,
683) -> TextOutcome {
684 HandleEvent::handle(state, event, MouseOnly)
685}