Skip to main content

azul_layout/widgets/
number_input.rs

1//! Same as TextInput, but only allows a number
2
3use std::string::String;
4
5use azul_core::{
6    callbacks::{CoreCallbackData, Update},
7    dom::Dom,
8    refany::RefAny,
9};
10use azul_css::{
11    dynamic_selector::CssPropertyWithConditionsVec,
12    props::{
13        basic::*,
14        layout::*,
15        property::{CssProperty, *},
16        style::*,
17    },
18    *,
19};
20
21use crate::{
22    callbacks::{Callback, CallbackInfo},
23    widgets::text_input::{
24        OnTextInputReturn, TextInput, TextInputOnFocusLostCallback,
25        TextInputOnFocusLostCallbackType, TextInputOnTextInputCallback,
26        TextInputOnTextInputCallbackType, TextInputOnVirtualKeyDownCallback,
27        TextInputOnVirtualKeyDownCallbackType, TextInputState, TextInputValid,
28    },
29};
30
31pub type NumberInputOnValueChangeCallbackType =
32    extern "C" fn(RefAny, CallbackInfo, NumberInputState) -> Update;
33impl_widget_callback!(
34    NumberInputOnValueChange,
35    OptionNumberInputOnValueChange,
36    NumberInputOnValueChangeCallback,
37    NumberInputOnValueChangeCallbackType
38);
39
40pub type NumberInputOnFocusLostCallbackType =
41    extern "C" fn(RefAny, CallbackInfo, NumberInputState) -> Update;
42impl_widget_callback!(
43    NumberInputOnFocusLost,
44    OptionNumberInputOnFocusLost,
45    NumberInputOnFocusLostCallback,
46    NumberInputOnFocusLostCallbackType
47);
48
49#[derive(Debug, Default, Clone, PartialEq)]
50#[repr(C)]
51pub struct NumberInput {
52    pub number_input_state: NumberInputStateWrapper,
53    pub text_input: TextInput,
54    pub style: CssPropertyWithConditionsVec,
55}
56
57#[derive(Debug, Default, Clone, PartialEq)]
58#[repr(C)]
59pub struct NumberInputStateWrapper {
60    pub inner: NumberInputState,
61    pub on_value_change: OptionNumberInputOnValueChange,
62    pub on_focus_lost: OptionNumberInputOnFocusLost,
63}
64
65#[derive(Debug, Clone, PartialEq)]
66#[repr(C)]
67pub struct NumberInputState {
68    pub previous: f32,
69    pub number: f32,
70    pub min: f32,
71    pub max: f32,
72}
73
74impl Default for NumberInputState {
75    fn default() -> Self {
76        Self {
77            previous: 0.0,
78            number: 0.0,
79            min: 0.0,
80            max: core::f32::MAX,
81        }
82    }
83}
84
85impl NumberInput {
86    pub fn create(input: f32) -> Self {
87        Self {
88            number_input_state: NumberInputStateWrapper {
89                inner: NumberInputState {
90                    number: input,
91                    ..Default::default()
92                },
93                ..Default::default()
94            },
95            ..Default::default()
96        }
97    }
98
99    pub fn set_on_text_input<C: Into<TextInputOnTextInputCallback>>(
100        &mut self,
101        refany: RefAny,
102        callback: C,
103    ) {
104        self.text_input.set_on_text_input(refany, callback);
105    }
106
107    pub fn with_on_text_input<C: Into<TextInputOnTextInputCallback>>(
108        mut self,
109        refany: RefAny,
110        callback: C,
111    ) -> Self {
112        self.set_on_text_input(refany, callback);
113        self
114    }
115
116    pub fn set_on_virtual_key_down<C: Into<TextInputOnVirtualKeyDownCallback>>(
117        &mut self,
118        refany: RefAny,
119        callback: C,
120    ) {
121        self.text_input.set_on_virtual_key_down(refany, callback);
122    }
123
124    pub fn with_on_virtual_key_down<C: Into<TextInputOnVirtualKeyDownCallback>>(
125        mut self,
126        refany: RefAny,
127        callback: C,
128    ) -> Self {
129        self.set_on_virtual_key_down(refany, callback);
130        self
131    }
132
133    pub fn set_placeholder_style(&mut self, style: CssPropertyWithConditionsVec) {
134        self.text_input.placeholder_style = style;
135    }
136
137    pub fn with_placeholder_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
138        self.set_placeholder_style(style);
139        self
140    }
141
142    pub fn set_container_style(&mut self, style: CssPropertyWithConditionsVec) {
143        self.text_input.container_style = style;
144    }
145
146    pub fn with_container_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
147        self.set_container_style(style);
148        self
149    }
150
151    pub fn set_label_style(&mut self, style: CssPropertyWithConditionsVec) {
152        self.text_input.label_style = style;
153    }
154
155    pub fn with_label_style(mut self, style: CssPropertyWithConditionsVec) -> Self {
156        self.set_label_style(style);
157        self
158    }
159
160    // Function called when the input has been parsed as a number
161    pub fn set_on_value_change<C: Into<NumberInputOnValueChangeCallback>>(
162        &mut self,
163        refany: RefAny,
164        callback: C,
165    ) {
166        self.number_input_state.on_value_change = Some(NumberInputOnValueChange {
167            callback: callback.into(),
168            refany,
169        })
170        .into();
171    }
172
173    pub fn with_on_value_change<C: Into<NumberInputOnValueChangeCallback>>(
174        mut self,
175        refany: RefAny,
176        callback: C,
177    ) -> Self {
178        self.set_on_value_change(refany, callback);
179        self
180    }
181
182    pub fn set_on_focus_lost<C: Into<NumberInputOnFocusLostCallback>>(
183        &mut self,
184        refany: RefAny,
185        callback: C,
186    ) {
187        self.number_input_state.on_focus_lost = Some(NumberInputOnFocusLost {
188            callback: callback.into(),
189            refany,
190        })
191        .into();
192    }
193
194    pub fn with_on_focus_lost<C: Into<NumberInputOnFocusLostCallback>>(
195        mut self,
196        refany: RefAny,
197        callback: C,
198    ) -> Self {
199        self.set_on_focus_lost(refany, callback);
200        self
201    }
202
203    pub fn swap_with_default(&mut self) -> Self {
204        let mut s = Self::create(0.0);
205        core::mem::swap(&mut s, self);
206        s
207    }
208
209    pub fn dom(mut self) -> Dom {
210        let number_string = format!("{}", self.number_input_state.inner.number);
211        self.text_input.text_input_state.inner.text = number_string
212            .chars()
213            .map(|s| s as u32)
214            .collect::<Vec<_>>()
215            .into();
216
217        let state = RefAny::new(self.number_input_state);
218
219        self.text_input.set_on_text_input(
220            state.clone(),
221            validate_text_input as TextInputOnTextInputCallbackType,
222        );
223        self.text_input
224            .set_on_focus_lost(state, on_focus_lost as TextInputOnFocusLostCallbackType);
225        self.text_input.dom()
226    }
227}
228
229extern "C" fn on_focus_lost(
230    mut refany: RefAny,
231    info: CallbackInfo,
232    _state: TextInputState,
233) -> Update {
234    let mut refany = match refany.downcast_mut::<NumberInputStateWrapper>() {
235        Some(s) => s,
236        None => return Update::DoNothing,
237    };
238
239    let result = {
240        let number_input = &mut *refany;
241        let onfocuslost = &mut number_input.on_focus_lost;
242        let inner = number_input.inner.clone();
243
244        match onfocuslost.as_mut() {
245            Some(NumberInputOnFocusLost { callback, refany }) => {
246                (callback.cb)(refany.clone(), info.clone(), inner)
247            }
248            None => Update::DoNothing,
249        }
250    };
251
252    result
253}
254
255extern "C" fn validate_text_input(
256    mut refany: RefAny,
257    info: CallbackInfo,
258    state: TextInputState,
259) -> OnTextInputReturn {
260    let mut refany = match refany.downcast_mut::<NumberInputStateWrapper>() {
261        Some(s) => s,
262        None => {
263            return OnTextInputReturn {
264                update: Update::DoNothing,
265                valid: TextInputValid::Yes,
266            };
267        }
268    };
269
270    let validated_input: String = state
271        .text
272        .iter()
273        .filter_map(|c| core::char::from_u32(*c))
274        .map(|c| if c == ',' { '.' } else { c })
275        .collect();
276
277    let validated_f32 = match validated_input.parse::<f32>() {
278        Ok(s) => s,
279        Err(_) => {
280            // do not re-layout the entire screen,
281            // but don't handle the character
282            return OnTextInputReturn {
283                update: Update::DoNothing,
284                valid: TextInputValid::No,
285            };
286        }
287    };
288
289    let result = {
290        let number_input = &mut *refany;
291        let onvaluechange = &mut number_input.on_value_change;
292        let inner = &mut number_input.inner;
293
294        inner.previous = inner.number;
295        inner.number = validated_f32;
296        let inner_clone = inner.clone();
297
298        match onvaluechange.as_mut() {
299            Some(NumberInputOnValueChange { callback, refany }) => {
300                (callback.cb)(refany.clone(), info.clone(), inner_clone)
301            }
302            None => Update::DoNothing,
303        }
304    };
305
306    OnTextInputReturn {
307        update: result,
308        valid: TextInputValid::Yes,
309    }
310}