flux_tui/components/
numeric.rs1use super::*;
2use crate::canvas::*;
3
4use crossterm::event::{Event, KeyCode, KeyModifiers};
5use std::rc::Rc;
6
7pub type NumericValueChangedCallback = dyn Fn(&mut Numeric);
8pub type NumericKeyHandler = fn(&mut Numeric, event: &mut WindowEvent);
9
10
11pub struct Numeric {
20 base: WidgetBase,
21 has_focus: bool,
22 value: i32,
23 stepcount: u8,
24 radix: Radix,
25 callback: Rc<NumericValueChangedCallback>,
26 key_handler: NumericKeyHandler,
27 focus_color: Option<Color>,
28 min_range: i32,
29 max_range: i32,
30}
31
32impl Default for Numeric {
33 fn default() -> Self {
34 let mut slf = Self {
35 base: WidgetBase::default(),
36 has_focus: false,
37 value: 0,
38 stepcount: 1,
39 radix: Radix::Decimal,
40 callback: Rc::new(Self::default_callback),
41 key_handler: Self::default_key_handler,
42 focus_color: None,
43 min_range: i32::MIN,
44 max_range: i32::MAX,
45 };
46 slf.base.constraints.x.min = NonZero::new(3).unwrap();
47 slf.value_changed_hook();
48 slf
49 }
50}
51
52impl Numeric {
53 pub const ARROW_UP: Grapheme = ScrollViewer::ARROW_UP;
54 pub const ARROW_DOWN: Grapheme = ScrollViewer::ARROW_DOWN;
55 pub const ARROW_UP_DISABLED: Grapheme = ScrollViewer::ARROW_UP_DISABLED;
56 pub const ARROW_DOWN_DISABLED: Grapheme = ScrollViewer::ARROW_DOWN_DISABLED;
57 pub const NUMERIC_PLACEHOLDER: Grapheme = Grapheme::new_unchecked("x", GlyphWidth::Half);
58
59 pub fn default_key_handler(&mut self, event: &mut WindowEvent) {
60 if let Event::Key(k) = event.raw() {
61 if k.modifiers.contains(KeyModifiers::ALT) {
62 if k.code == KeyCode::Up {
63 self.step_up();
64 event.handled = true;
65 }
66 else if k.code == KeyCode::Down {
67 self.step_down();
68 event.handled = true;
69 }
70 }
71 }
72 }
73
74 pub fn default_callback(_: &mut Self) {}
75
76 pub fn set_changed_value_callback<F: Fn(&mut Numeric) + 'static>(&mut self, callback: F) {
77 self.callback = Rc::new(callback);
78 }
79
80 pub fn set_key_handler(&mut self, f: NumericKeyHandler) {
81 self.key_handler = f;
82 }
83
84 pub fn set_min_range(&mut self, min_range: i32) {
85 if self.min_range < self.max_range {
86 self.min_range = min_range;
87 if !(self.min_range..self.max_range).contains(&self.value) {
88 self.value = self.min_range;
89 self.value_changed_hook();
90 }
91 }
92 }
93
94 pub fn set_max_range(&mut self, max_range: i32) {
95 if self.max_range > self.min_range {
96 self.max_range = max_range;
97 if !(self.min_range..self.max_range).contains(&self.value) {
98 self.value = self.min_range;
99 self.value_changed_hook();
100 }
101 }
102 }
103
104 pub fn set_value(&mut self, value: i32) {
105 self.value = value;
106 self.value_changed_hook();
107 }
108
109 pub fn get_value(&self) -> i32 {
110 self.value
111 }
112
113 pub fn set_base(&mut self, base: Radix) {
114 if base != self.radix {
115 self.radix = base;
116 self.value_changed_hook();
117 }
118 }
119
120 pub fn get_base(&self) -> Radix {
121 self.radix
122 }
123
124 pub fn step_up(&mut self) {
125 let cb = self.callback.clone();
126 if self.value <= self.max_range - self.stepcount as i32 {
127 self.value += self.stepcount as i32;
128 }
129 else {
130 self.value = self.max_range;
131 }
132 self.value_changed_hook();
133 cb(self);
134 }
135
136 pub fn step_down(&mut self) {
137 let cb = self.callback.clone();
138 if self.value > self.min_range + self.stepcount as i32 {
139 self.value -= self.stepcount as i32;
140 }
141 else {
142 self.value = self.min_range;
143 }
144 self.value_changed_hook();
145 cb(self);
146 }
147
148 pub fn get_stepcount(&self) -> u8 {
149 self.stepcount
150 }
151
152 pub fn set_stepcount(&mut self, stepcount: u8) {
153 self.stepcount = stepcount;
154 }
155
156 pub fn set_focus_color(&mut self, color: Option<Color>) {
157 self.focus_color = color;
158 }
159
160 fn value_changed_hook(&mut self) {
161 let old = self.base.constraints.x.max;
162 self.base.constraints.x.max = Size::Fixed(
163 NonZero::new(
164 self.radix.prefix_glyphs().len() as TSize
165 + self
166 .value
167 .abs()
168 .checked_ilog(self.radix as i32)
169 .unwrap_or(0) as TSize
170 + self.base.constraints.x.min.get()
171 + self.value.is_negative() as TSize,
172 )
173 .unwrap(),
174 );
175 if old != self.base.constraints.x.max {
176 self.provoke_changed_property(WindowProperty::Size);
177 }
178 }
179}
180
181impl Window for Numeric {
182 fn render(&self, canvas: &mut Canvas) {
183 let len = self
184 .value
185 .abs()
186 .checked_ilog(self.radix as i32)
187 .unwrap_or(0) as TSize
188 + 1;
189 let updown = 2;
190
191 let mut row = canvas.get_row(0, GlyphWidth::Half).unwrap();
192
193 row.get(row.length() - 1)
194 .unwrap()
195 .set_grapheme(Self::ARROW_UP)
196 .unwrap();
197 row.get(row.length() - 2)
198 .unwrap()
199 .set_grapheme(Self::ARROW_DOWN)
200 .unwrap();
201 if self.value == self.max_range {
202 row.get(row.length() - 1)
203 .unwrap()
204 .set_grapheme(Self::ARROW_UP_DISABLED)
205 .unwrap();
206 }
207 else if self.value == self.min_range {
208 row.get(row.length() - 2)
209 .unwrap()
210 .set_grapheme(Self::ARROW_DOWN_DISABLED)
211 .unwrap();
212 }
213
214 if row.length()
215 >= len
216 + updown + self.value.is_negative() as TSize
217 + self.radix.prefix_glyphs().len() as TSize
218 {
219 let focus_color = match self.has_focus {
220 true => self.focus_color,
221 false => None,
222 };
223 let mut start = 0;
224 if self.value < 0 {
225 let mut item = row.get(0).unwrap();
226 item.set_grapheme(Radix::NEGATIVE_INDICATOR).unwrap();
227 start += 1;
228 if let Some(color) = focus_color {
229 item.set_fg(color);
230 }
231 }
232 let end = row.length() - updown;
233
234 for g in self.radix.prefix_glyphs() {
235 let mut item = row.get(start).unwrap();
236 item.set_grapheme(*g).unwrap();
237 start += 1;
238 }
239
240 let mut rem = self.value.abs();
241 for idx in (start..end).rev() {
242 let mut item = row.get(idx).unwrap();
243 item.set_grapheme(self.radix.first_literal(rem as usize))
244 .unwrap();
245 rem /= self.radix as i32;
246 }
247 }
248 else {
249 let mut item = row.get(row.length() - 3).unwrap();
250 item.set_grapheme(Self::NUMERIC_PLACEHOLDER).unwrap();
251 }
252
253 if let Some(color) = self.focus_color
254 && self.has_focus
255 {
256 row.fill_foreground(color);
257 }
258 else if let Some(color) = self.base.colors.base_fg {
259 row.fill_foreground(color);
260 }
261
262 if let Some(color) = self.base.colors.disabled
263 && !self.base.enabled
264 {
265 row.fill_foreground(color);
266 }
267
268 if let Some(color) = self.base.colors.base_bg {
269 row.fill_background(color);
270 }
271 }
272
273 fn handle_event(&mut self, event: &mut WindowEvent) {
274 match event.raw() {
275 Event::FocusGained => self.has_focus = true,
276 Event::FocusLost => self.has_focus = false,
277 _ => (self.key_handler)(self, event),
278 }
279 }
280
281 fn is_enabled(&self) -> bool {
282 self.base.enabled
283 }
284}
285
286impl WindowLayout for Numeric {
287 fn desired_size(&self, available_size: TPoint) -> TPoint {
288 self.base.desired_size(available_size)
289 }
290
291 fn border(&self) -> BorderStyle {
292 self.base.border
293 }
294
295 fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
296 self.base.alignment
297 }
298
299 fn is_visible(&self) -> bool {
300 self.base.visibility
301 }
302
303 fn margin(&self) -> Thickness {
304 self.base.margin
305 }
306}
307
308impl HasWindowUID for Numeric {
309 fn uid(&self) -> WindowUID {
310 self.base.uid
311 }
312}
313
314impl Widget for Numeric {
315 fn set_visibility(&mut self, visibility: bool) {
316 self.base.visibility = visibility;
317 self.provoke_changed_property(WindowProperty::IsVisible);
318 }
319
320 fn set_width(&mut self, _: Size) {}
322
323 fn set_height(&mut self, _: Size) {}
325
326 fn set_margin(&mut self, margin: Thickness) {
327 self.base.margin = margin;
328 self.provoke_changed_property(WindowProperty::Margin);
329 }
330
331 fn set_border(&mut self, border: BorderStyle) {
332 self.base.border = border;
333 self.provoke_changed_property(WindowProperty::Border);
334 }
335
336 fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
337 self.base.alignment = (horizontal, vertical);
338 self.provoke_changed_property(WindowProperty::Alignment);
339 }
340
341 fn set_enabled_state(&mut self, is_enabled: bool) {
342 self.base.enabled = is_enabled;
343 }
344
345 fn set_width_constraint(&mut self, _: SizeConstraint) {}
347
348 fn set_height_constraint(&mut self, _: SizeConstraint) {}
350}
351
352impl WidgetColors for Numeric {
353 fn set_disabled_color(&mut self, color: Option<Color>) {
354 self.base.colors.disabled = color;
355 }
356
357 fn set_base_fg_color(&mut self, color: Option<Color>) {
358 self.base.colors.base_fg = color;
359 }
360
361 fn set_base_bg_color(&mut self, color: Option<Color>) {
362 self.base.colors.base_bg = color;
363 }
364}