1use crate::_private::NonExhaustive;
5use crate::event::{ReadOnly, TextOutcome};
6use crate::text_input_mask::{MaskedInput, MaskedInputState};
7use crate::{
8 TextFocusGained, TextFocusLost, TextStyle, TextTab, derive_text_widget,
9 derive_text_widget_state,
10};
11use format_num_pattern::{NumberFmtError, NumberFormat, NumberSymbols};
12use rat_event::{HandleEvent, MouseOnly, Regular};
13use ratatui::buffer::Buffer;
14use ratatui::layout::Rect;
15use ratatui::style::Style;
16use ratatui::widgets::Block;
17use ratatui::widgets::StatefulWidget;
18use std::fmt::{Debug, Display, LowerExp};
19use std::str::FromStr;
20
21#[derive(Debug, Default, Clone)]
30pub struct NumberInput<'a> {
31 widget: MaskedInput<'a>,
32}
33
34#[derive(Debug, Clone)]
36pub struct NumberInputState {
37 pub area: Rect,
40 pub inner: Rect,
43
44 pub widget: MaskedInputState,
45
46 pattern: String,
48 locale: format_num_pattern::Locale,
50 format: NumberFormat,
54
55 pub non_exhaustive: NonExhaustive,
56}
57
58derive_text_widget!(NumberInput<'a>);
59
60impl<'a> NumberInput<'a> {
61 pub fn new() -> Self {
62 Self::default()
63 }
64
65 #[inline]
67 pub fn compact(mut self, compact: bool) -> Self {
68 self.widget = self.widget.compact(compact);
69 self
70 }
71
72 pub fn width(&self, state: &NumberInputState) -> u16 {
74 state.widget.mask.len() as u16 + 1
75 }
76
77 pub fn height(&self) -> u16 {
79 1
80 }
81}
82
83impl<'a> StatefulWidget for &NumberInput<'a> {
84 type State = NumberInputState;
85
86 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
87 (&self.widget).render(area, buf, &mut state.widget);
88
89 state.area = state.widget.area;
90 state.inner = state.widget.inner;
91 }
92}
93
94impl StatefulWidget for NumberInput<'_> {
95 type State = NumberInputState;
96
97 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
98 self.widget.render(area, buf, &mut state.widget);
99
100 state.area = state.widget.area;
101 state.inner = state.widget.inner;
102 }
103}
104
105derive_text_widget_state!(NumberInputState);
106
107impl Default for NumberInputState {
108 fn default() -> Self {
109 let mut s = Self {
110 area: Default::default(),
111 inner: Default::default(),
112 widget: Default::default(),
113 pattern: "".to_string(),
114 locale: Default::default(),
115 format: Default::default(),
116 non_exhaustive: NonExhaustive,
117 };
118 _ = s.set_format("#####");
119 s
120 }
121}
122
123impl NumberInputState {
124 pub fn new() -> Self {
125 Self::default()
126 }
127
128 pub fn new_pattern<S: AsRef<str>>(pattern: S) -> Result<Self, NumberFmtError> {
129 let mut s = Self::default();
130 s.set_format(pattern)?;
131 Ok(s)
132 }
133
134 pub fn new_loc_pattern<S: AsRef<str>>(
135 pattern: S,
136 locale: format_num_pattern::Locale,
137 ) -> Result<Self, NumberFmtError> {
138 let mut s = Self::default();
139 s.set_format_loc(pattern.as_ref(), locale)?;
140 Ok(s)
141 }
142
143 pub fn named(name: &str) -> Self {
144 let mut z = Self::default();
145 z.widget.focus = z.widget.focus.with_name(name);
146 z
147 }
148
149 pub fn with_pattern<S: AsRef<str>>(mut self, pattern: S) -> Result<Self, NumberFmtError> {
150 self.set_format(pattern)?;
151 Ok(self)
152 }
153
154 pub fn with_loc_pattern<S: AsRef<str>>(
155 mut self,
156 pattern: S,
157 locale: format_num_pattern::Locale,
158 ) -> Result<Self, NumberFmtError> {
159 self.set_format_loc(pattern.as_ref(), locale)?;
160 Ok(self)
161 }
162
163 #[inline]
165 pub fn format(&self) -> &str {
166 self.pattern.as_str()
167 }
168
169 #[inline]
171 pub fn locale(&self) -> chrono::Locale {
172 self.locale
173 }
174
175 pub fn set_format<S: AsRef<str>>(&mut self, pattern: S) -> Result<(), NumberFmtError> {
177 self.set_format_loc(pattern, format_num_pattern::Locale::default())
178 }
179
180 pub fn set_format_loc<S: AsRef<str>>(
182 &mut self,
183 pattern: S,
184 locale: format_num_pattern::Locale,
185 ) -> Result<(), NumberFmtError> {
186 let sym = NumberSymbols::monetary(locale);
187
188 self.format = NumberFormat::new(pattern.as_ref())?;
189 self.widget.set_mask(pattern.as_ref())?;
190 self.widget.set_num_symbols(sym);
191
192 Ok(())
193 }
194}
195
196impl NumberInputState {
197 pub fn value_opt<T: FromStr>(&self) -> Result<Option<T>, NumberFmtError> {
200 let s = self.widget.text();
201 if s.trim().is_empty() {
202 Ok(None)
203 } else {
204 self.format.parse(s).map(|v| Some(v))
205 }
206 }
207
208 pub fn value<T: FromStr>(&self) -> Result<T, NumberFmtError> {
210 let s = self.widget.text();
211 self.format.parse(s)
212 }
213}
214
215impl NumberInputState {
216 #[inline]
218 pub fn clear(&mut self) {
219 self.widget.clear();
220 }
221
222 pub fn set_value<T: LowerExp + Display + Debug>(
224 &mut self,
225 number: T,
226 ) -> Result<(), NumberFmtError> {
227 let s = self.format.fmt(number)?;
228 self.widget.set_text(s);
229 Ok(())
230 }
231}
232
233impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for NumberInputState {
234 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
235 self.widget.handle(event, Regular)
236 }
237}
238
239impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for NumberInputState {
240 fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
241 self.widget.handle(event, ReadOnly)
242 }
243}
244
245impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for NumberInputState {
246 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
247 self.widget.handle(event, MouseOnly)
248 }
249}
250
251pub fn handle_events(
255 state: &mut NumberInputState,
256 focus: bool,
257 event: &crossterm::event::Event,
258) -> TextOutcome {
259 state.widget.focus.set(focus);
260 HandleEvent::handle(state, event, Regular)
261}
262
263pub fn handle_readonly_events(
267 state: &mut NumberInputState,
268 focus: bool,
269 event: &crossterm::event::Event,
270) -> TextOutcome {
271 state.widget.focus.set(focus);
272 state.handle(event, ReadOnly)
273}
274
275pub fn handle_mouse_events(
277 state: &mut NumberInputState,
278 event: &crossterm::event::Event,
279) -> TextOutcome {
280 HandleEvent::handle(state, event, MouseOnly)
281}