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::{HasScreenCursor, TextError, TextFocusGained, TextFocusLost, TextStyle, upos_type};
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::style::Style;
18use ratatui::widgets::Block;
19use ratatui::widgets::StatefulWidget;
20use std::fmt::{Debug, Display, LowerExp};
21use std::ops::Range;
22use std::str::FromStr;
23
24#[derive(Debug, Default, Clone)]
33pub struct NumberInput<'a> {
34 widget: MaskedInput<'a>,
35}
36
37#[derive(Debug, Clone)]
39pub struct NumberInputState {
40 pub widget: MaskedInputState,
41
42 pattern: String,
44 locale: format_num_pattern::Locale,
46 format: NumberFormat,
50
51 pub non_exhaustive: NonExhaustive,
52}
53
54impl<'a> NumberInput<'a> {
55 pub fn new() -> Self {
56 Self::default()
57 }
58
59 #[inline]
61 pub fn compact(mut self, compact: bool) -> Self {
62 self.widget = self.widget.compact(compact);
63 self
64 }
65
66 #[inline]
68 pub fn styles(mut self, style: TextStyle) -> Self {
69 self.widget = self.widget.styles(style);
70 self
71 }
72
73 #[inline]
75 pub fn style(mut self, style: impl Into<Style>) -> Self {
76 self.widget = self.widget.style(style);
77 self
78 }
79
80 #[inline]
82 pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
83 self.widget = self.widget.focus_style(style);
84 self
85 }
86
87 #[inline]
89 pub fn select_style(mut self, style: impl Into<Style>) -> Self {
90 self.widget = self.widget.select_style(style);
91 self
92 }
93
94 #[inline]
96 pub fn invalid_style(mut self, style: impl Into<Style>) -> Self {
97 self.widget = self.widget.invalid_style(style);
98 self
99 }
100
101 #[inline]
102 pub fn block(mut self, block: Block<'a>) -> Self {
103 self.widget = self.widget.block(block);
104 self
105 }
106
107 #[inline]
109 pub fn on_focus_gained(mut self, of: TextFocusGained) -> Self {
110 self.widget = self.widget.on_focus_gained(of);
111 self
112 }
113
114 #[inline]
116 pub fn on_focus_lost(mut self, of: TextFocusLost) -> Self {
117 self.widget = self.widget.on_focus_lost(of);
118 self
119 }
120}
121
122impl<'a> StatefulWidget for &NumberInput<'a> {
123 type State = NumberInputState;
124
125 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
126 (&self.widget).render(area, buf, &mut state.widget);
127 }
128}
129
130impl StatefulWidget for NumberInput<'_> {
131 type State = NumberInputState;
132
133 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
134 self.widget.render(area, buf, &mut state.widget);
135 }
136}
137
138impl Default for NumberInputState {
139 fn default() -> Self {
140 let mut s = Self {
141 widget: Default::default(),
142 pattern: "".to_string(),
143 locale: Default::default(),
144 format: Default::default(),
145 non_exhaustive: NonExhaustive,
146 };
147 _ = s.set_format("#####");
148 s
149 }
150}
151
152impl HasFocus for NumberInputState {
153 fn build(&self, builder: &mut FocusBuilder) {
154 builder.leaf_widget(self);
155 }
156
157 #[inline]
158 fn focus(&self) -> FocusFlag {
159 self.widget.focus.clone()
160 }
161
162 #[inline]
163 fn area(&self) -> Rect {
164 self.widget.area
165 }
166
167 fn navigable(&self) -> Navigation {
168 self.widget.navigable()
169 }
170}
171
172impl NumberInputState {
173 pub fn new() -> Self {
174 Self::default()
175 }
176
177 pub fn new_pattern<S: AsRef<str>>(pattern: S) -> Result<Self, NumberFmtError> {
178 let mut s = Self::default();
179 s.set_format(pattern)?;
180 Ok(s)
181 }
182
183 pub fn new_loc_pattern<S: AsRef<str>>(
184 pattern: S,
185 locale: format_num_pattern::Locale,
186 ) -> Result<Self, NumberFmtError> {
187 let mut s = Self::default();
188 s.set_format_loc(pattern.as_ref(), locale)?;
189 Ok(s)
190 }
191
192 pub fn named(name: &str) -> Self {
193 Self {
194 widget: MaskedInputState::named(name),
195 ..Default::default()
196 }
197 }
198
199 pub fn with_pattern<S: AsRef<str>>(mut self, pattern: S) -> Result<Self, NumberFmtError> {
200 self.set_format(pattern)?;
201 Ok(self)
202 }
203
204 pub fn with_loc_pattern<S: AsRef<str>>(
205 mut self,
206 pattern: S,
207 locale: format_num_pattern::Locale,
208 ) -> Result<Self, NumberFmtError> {
209 self.set_format_loc(pattern.as_ref(), locale)?;
210 Ok(self)
211 }
212
213 #[inline]
215 pub fn format(&self) -> &str {
216 self.pattern.as_str()
217 }
218
219 #[inline]
221 pub fn locale(&self) -> chrono::Locale {
222 self.locale
223 }
224
225 pub fn set_format<S: AsRef<str>>(&mut self, pattern: S) -> Result<(), NumberFmtError> {
227 self.set_format_loc(pattern, format_num_pattern::Locale::default())
228 }
229
230 pub fn set_format_loc<S: AsRef<str>>(
232 &mut self,
233 pattern: S,
234 locale: format_num_pattern::Locale,
235 ) -> Result<(), NumberFmtError> {
236 let sym = NumberSymbols::monetary(locale);
237
238 self.format = NumberFormat::new(pattern.as_ref())?;
239 self.widget.set_mask(pattern.as_ref())?;
240 self.widget.set_num_symbols(sym);
241
242 Ok(())
243 }
244
245 #[inline]
247 pub fn set_invalid(&mut self, invalid: bool) {
248 self.widget.invalid = invalid;
249 }
250
251 #[inline]
253 pub fn get_invalid(&self) -> bool {
254 self.widget.invalid
255 }
256
257 #[inline]
261 pub fn set_overwrite(&mut self, overwrite: bool) {
262 self.widget.set_overwrite(overwrite);
263 }
264
265 #[inline]
267 pub fn overwrite(&self) -> bool {
268 self.widget.overwrite()
269 }
270}
271
272impl NumberInputState {
273 #[inline]
276 pub fn set_clipboard(&mut self, clip: Option<impl Clipboard + 'static>) {
277 self.widget.set_clipboard(clip);
278 }
279
280 #[inline]
283 pub fn clipboard(&self) -> Option<&dyn Clipboard> {
284 self.widget.clipboard()
285 }
286
287 #[inline]
289 pub fn copy_to_clip(&mut self) -> bool {
290 self.widget.copy_to_clip()
291 }
292
293 #[inline]
295 pub fn cut_to_clip(&mut self) -> bool {
296 self.widget.cut_to_clip()
297 }
298
299 #[inline]
301 pub fn paste_from_clip(&mut self) -> bool {
302 self.widget.paste_from_clip()
303 }
304}
305
306impl NumberInputState {
307 #[inline]
309 pub fn set_undo_buffer(&mut self, undo: Option<impl UndoBuffer + 'static>) {
310 self.widget.set_undo_buffer(undo);
311 }
312
313 #[inline]
315 pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
316 self.widget.undo_buffer()
317 }
318
319 #[inline]
321 pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
322 self.widget.undo_buffer_mut()
323 }
324
325 #[inline]
327 pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
328 self.widget.recent_replay_log()
329 }
330
331 #[inline]
333 pub fn replay_log(&mut self, replay: &[UndoEntry]) {
334 self.widget.replay_log(replay)
335 }
336
337 #[inline]
339 pub fn undo(&mut self) -> bool {
340 self.widget.undo()
341 }
342
343 #[inline]
345 pub fn redo(&mut self) -> bool {
346 self.widget.redo()
347 }
348}
349
350impl NumberInputState {
351 #[inline]
353 pub fn set_styles(&mut self, styles: Vec<(Range<usize>, usize)>) {
354 self.widget.set_styles(styles);
355 }
356
357 #[inline]
360 pub fn add_style(&mut self, range: Range<usize>, style: usize) {
361 self.widget.add_style(range, style);
362 }
363
364 #[inline]
367 pub fn add_range_style(
368 &mut self,
369 range: Range<upos_type>,
370 style: usize,
371 ) -> Result<(), TextError> {
372 self.widget.add_range_style(range, style)
373 }
374
375 #[inline]
377 pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
378 self.widget.remove_style(range, style);
379 }
380
381 #[inline]
383 pub fn remove_range_style(
384 &mut self,
385 range: Range<upos_type>,
386 style: usize,
387 ) -> Result<(), TextError> {
388 self.widget.remove_range_style(range, style)
389 }
390
391 pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
393 self.widget.styles_in(range, buf)
394 }
395
396 #[inline]
398 pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
399 self.widget.styles_at(byte_pos, buf)
400 }
401
402 #[inline]
405 pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
406 self.widget.styles_at_match(byte_pos, style)
407 }
408
409 #[inline]
411 pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
412 self.widget.styles()
413 }
414}
415
416impl NumberInputState {
417 #[inline]
419 pub fn offset(&self) -> upos_type {
420 self.widget.offset()
421 }
422
423 #[inline]
425 pub fn set_offset(&mut self, offset: upos_type) {
426 self.widget.set_offset(offset)
427 }
428
429 #[inline]
431 pub fn cursor(&self) -> upos_type {
432 self.widget.cursor()
433 }
434
435 #[inline]
437 pub fn set_cursor(&mut self, cursor: upos_type, extend_selection: bool) -> bool {
438 self.widget.set_cursor(cursor, extend_selection)
439 }
440
441 #[inline]
443 pub fn set_default_cursor(&mut self) {
444 self.widget.set_default_cursor()
445 }
446
447 #[inline]
449 pub fn anchor(&self) -> upos_type {
450 self.widget.anchor()
451 }
452
453 #[inline]
455 pub fn has_selection(&self) -> bool {
456 self.widget.has_selection()
457 }
458
459 #[inline]
461 pub fn selection(&self) -> Range<upos_type> {
462 self.widget.selection()
463 }
464
465 #[inline]
467 pub fn set_selection(&mut self, anchor: upos_type, cursor: upos_type) -> bool {
468 self.widget.set_selection(anchor, cursor)
469 }
470
471 #[inline]
473 pub fn select_all(&mut self) {
474 self.widget.select_all();
475 }
476
477 #[inline]
479 pub fn selected_text(&self) -> &str {
480 self.widget.selected_text()
481 }
482}
483
484impl NumberInputState {
485 #[inline]
487 pub fn is_empty(&self) -> bool {
488 self.widget.is_empty()
489 }
490
491 pub fn value_opt<T: FromStr>(&self) -> Result<Option<T>, NumberFmtError> {
494 let s = self.widget.text();
495 if s.trim().is_empty() {
496 Ok(None)
497 } else {
498 self.format.parse(s).map(|v| Some(v))
499 }
500 }
501
502 pub fn value<T: FromStr>(&self) -> Result<T, NumberFmtError> {
504 let s = self.widget.text();
505 self.format.parse(s)
506 }
507
508 #[inline]
510 pub fn len(&self) -> upos_type {
511 self.widget.len()
512 }
513
514 #[inline]
516 pub fn line_width(&self) -> upos_type {
517 self.widget.line_width()
518 }
519}
520
521impl NumberInputState {
522 #[inline]
524 pub fn clear(&mut self) {
525 self.widget.clear();
526 }
527
528 pub fn set_value<T: LowerExp + Display + Debug>(
530 &mut self,
531 number: T,
532 ) -> Result<(), NumberFmtError> {
533 let s = self.format.fmt(number)?;
534 self.widget.set_text(s);
535 Ok(())
536 }
537
538 #[inline]
540 pub fn insert_char(&mut self, c: char) -> bool {
541 self.widget.insert_char(c)
542 }
543
544 #[inline]
547 pub fn delete_range(&mut self, range: Range<upos_type>) -> bool {
548 self.widget.delete_range(range)
549 }
550
551 #[inline]
554 pub fn try_delete_range(&mut self, range: Range<upos_type>) -> Result<bool, TextError> {
555 self.widget.try_delete_range(range)
556 }
557}
558
559impl NumberInputState {
560 #[inline]
562 pub fn delete_next_char(&mut self) -> bool {
563 self.widget.delete_next_char()
564 }
565
566 #[inline]
568 pub fn delete_prev_char(&mut self) -> bool {
569 self.widget.delete_prev_char()
570 }
571
572 #[inline]
574 pub fn move_right(&mut self, extend_selection: bool) -> bool {
575 self.widget.move_right(extend_selection)
576 }
577
578 #[inline]
580 pub fn move_left(&mut self, extend_selection: bool) -> bool {
581 self.widget.move_left(extend_selection)
582 }
583
584 #[inline]
586 pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
587 self.widget.move_to_line_start(extend_selection)
588 }
589
590 #[inline]
592 pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
593 self.widget.move_to_line_end(extend_selection)
594 }
595}
596
597impl HasScreenCursor for NumberInputState {
598 #[inline]
600 fn screen_cursor(&self) -> Option<(u16, u16)> {
601 self.widget.screen_cursor()
602 }
603}
604
605impl RelocatableState for NumberInputState {
606 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
607 self.widget.relocate(shift, clip);
608 }
609}
610
611impl NumberInputState {
612 #[inline]
615 pub fn col_to_screen(&self, pos: upos_type) -> Option<u16> {
616 self.widget.col_to_screen(pos)
617 }
618
619 #[inline]
622 pub fn screen_to_col(&self, scx: i16) -> upos_type {
623 self.widget.screen_to_col(scx)
624 }
625
626 #[inline]
630 pub fn set_screen_cursor(&mut self, cursor: i16, extend_selection: bool) -> bool {
631 self.widget.set_screen_cursor(cursor, extend_selection)
632 }
633}
634
635impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for NumberInputState {
636 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
637 self.widget.handle(event, Regular)
638 }
639}
640
641impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for NumberInputState {
642 fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
643 self.widget.handle(event, ReadOnly)
644 }
645}
646
647impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for NumberInputState {
648 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
649 self.widget.handle(event, MouseOnly)
650 }
651}
652
653pub fn handle_events(
657 state: &mut NumberInputState,
658 focus: bool,
659 event: &crossterm::event::Event,
660) -> TextOutcome {
661 state.widget.focus.set(focus);
662 HandleEvent::handle(state, event, Regular)
663}
664
665pub fn handle_readonly_events(
669 state: &mut NumberInputState,
670 focus: bool,
671 event: &crossterm::event::Event,
672) -> TextOutcome {
673 state.widget.focus.set(focus);
674 state.handle(event, ReadOnly)
675}
676
677pub fn handle_mouse_events(
679 state: &mut NumberInputState,
680 event: &crossterm::event::Event,
681) -> TextOutcome {
682 HandleEvent::handle(state, event, MouseOnly)
683}