Skip to main content

calculator/
calculator.rs

1/*!
2    A calculator that use the grid layout of NWG.
3*/
4
5extern crate native_windows_gui2 as nwg;
6use nwg::NativeUi;
7
8#[derive(Debug)]
9enum Token {
10    Number(i32),
11    Plus,
12    Minus,
13    Mult,
14    Div,
15}
16
17#[derive(Default)]
18pub struct Calculator {
19    window: nwg::Window,
20
21    layout: nwg::GridLayout,
22
23    input: nwg::TextInput,
24
25    btn0: nwg::Button,
26    btn1: nwg::Button,
27    btn2: nwg::Button,
28    btn3: nwg::Button,
29    btn4: nwg::Button,
30    btn5: nwg::Button,
31    btn6: nwg::Button,
32    btn7: nwg::Button,
33    btn8: nwg::Button,
34    btn9: nwg::Button,
35
36    btn_plus: nwg::Button,
37    btn_minus: nwg::Button,
38    btn_mult: nwg::Button,
39    btn_divide: nwg::Button,
40    btn_process: nwg::Button,
41    btn_clear: nwg::Button,
42}
43
44impl Calculator {
45    fn number(&self, button: &nwg::Button) {
46        let text = self.input.text();
47        self.input.set_text(&format!("{}{}", text, button.text()));
48    }
49
50    fn clear(&self) {
51        self.input.set_text("");
52    }
53
54    fn compute(&self) {
55        use Token::*;
56        static SYMBOLS: &'static [char] = &['+', '-', '*', '/'];
57
58        let eq = self.input.text();
59        if eq.len() == 0 {
60            return;
61        }
62
63        let mut tokens: Vec<Token> = Vec::with_capacity(5);
64        let mut last = 0;
65
66        for (i, chr) in eq.char_indices() {
67            if SYMBOLS.iter().any(|&s| s == chr) {
68                let left = &eq[last..i];
69                match left.parse::<i32>() {
70                    Ok(i) => tokens.push(Token::Number(i)),
71                    _ => {
72                        nwg::error_message("Error", "Invalid equation!");
73                        self.input.set_text("");
74                        return;
75                    }
76                }
77
78                let tk = match chr {
79                    '+' => Plus,
80                    '-' => Minus,
81                    '*' => Mult,
82                    '/' => Div,
83                    _ => unreachable!(),
84                };
85
86                tokens.push(tk);
87
88                last = i + 1;
89            }
90        }
91
92        let right = &eq[last..];
93        match right.parse::<i32>() {
94            Ok(i) => tokens.push(Token::Number(i)),
95            _ => {
96                nwg::error_message("Error", "Invalid equation!");
97                self.input.set_text("");
98                return;
99            }
100        }
101
102        let mut i = 1;
103        let mut result = match &tokens[0] {
104            Token::Number(n) => *n,
105            _ => unreachable!(),
106        };
107        while i < tokens.len() {
108            match [&tokens[i], &tokens[i + 1]] {
109                [Plus, Number(n)] => {
110                    result += n;
111                }
112                [Minus, Number(n)] => {
113                    result -= n;
114                }
115                [Mult, Number(n)] => {
116                    result *= n;
117                }
118                [Div, Number(n)] => {
119                    result /= n;
120                }
121                _ => unreachable!(),
122            }
123            i += 2;
124        }
125
126        self.input.set_text(&result.to_string());
127    }
128
129    fn exit(&self) {
130        nwg::stop_thread_dispatch();
131    }
132}
133
134//
135// ALL of this stuff is handled by native-windows-derive
136//
137mod calculator_ui {
138    use super::*;
139    use native_windows_gui2 as nwg;
140    use std::cell::RefCell;
141    use std::ops::Deref;
142    use std::rc::Rc;
143
144    pub struct CalculatorUi {
145        inner: Rc<Calculator>,
146        default_handler: RefCell<Vec<nwg::EventHandler>>,
147    }
148
149    impl nwg::NativeUi<CalculatorUi> for Calculator {
150        fn build_ui(mut data: Calculator) -> Result<CalculatorUi, nwg::NwgError> {
151            use nwg::Event as E;
152
153            // Controls
154            nwg::Window::builder()
155                .size((300, 150))
156                .position((300, 300))
157                .title("Calculator")
158                .build(&mut data.window)?;
159
160            nwg::TextInput::builder()
161                .text("")
162                .align(nwg::HTextAlign::Right)
163                .readonly(true)
164                .parent(&data.window)
165                .build(&mut data.input)?;
166
167            nwg::Button::builder()
168                .text("1")
169                .parent(&data.window)
170                .focus(true)
171                .build(&mut data.btn1)?;
172
173            nwg::Button::builder()
174                .text("2")
175                .parent(&data.window)
176                .build(&mut data.btn2)?;
177            nwg::Button::builder()
178                .text("3")
179                .parent(&data.window)
180                .build(&mut data.btn3)?;
181            nwg::Button::builder()
182                .text("4")
183                .parent(&data.window)
184                .build(&mut data.btn4)?;
185            nwg::Button::builder()
186                .text("5")
187                .parent(&data.window)
188                .build(&mut data.btn5)?;
189            nwg::Button::builder()
190                .text("6")
191                .parent(&data.window)
192                .build(&mut data.btn6)?;
193            nwg::Button::builder()
194                .text("7")
195                .parent(&data.window)
196                .build(&mut data.btn7)?;
197            nwg::Button::builder()
198                .text("8")
199                .parent(&data.window)
200                .build(&mut data.btn8)?;
201            nwg::Button::builder()
202                .text("9")
203                .parent(&data.window)
204                .build(&mut data.btn9)?;
205            nwg::Button::builder()
206                .text("0")
207                .parent(&data.window)
208                .build(&mut data.btn0)?;
209
210            nwg::Button::builder()
211                .text("+")
212                .parent(&data.window)
213                .build(&mut data.btn_plus)?;
214            nwg::Button::builder()
215                .text("-")
216                .parent(&data.window)
217                .build(&mut data.btn_minus)?;
218            nwg::Button::builder()
219                .text("*")
220                .parent(&data.window)
221                .build(&mut data.btn_mult)?;
222            nwg::Button::builder()
223                .text("/")
224                .parent(&data.window)
225                .build(&mut data.btn_divide)?;
226            nwg::Button::builder()
227                .text("Clear")
228                .parent(&data.window)
229                .build(&mut data.btn_clear)?;
230            nwg::Button::builder()
231                .text("=")
232                .parent(&data.window)
233                .build(&mut data.btn_process)?;
234
235            // Wrap-up
236            let ui = CalculatorUi {
237                inner: Rc::new(data),
238                default_handler: Default::default(),
239            };
240
241            // Events
242            let window_handles = [&ui.window.handle];
243            for handle in window_handles.iter() {
244                let evt_ui = Rc::downgrade(&ui.inner);
245                let handle_events = move |evt, _evt_data, handle| {
246                    if let Some(evt_ui) = evt_ui.upgrade() {
247                        match evt {
248                            E::OnButtonClick => {
249                                if &handle == &evt_ui.btn0 {
250                                    Calculator::number(&evt_ui, &evt_ui.btn0);
251                                } else if &handle == &evt_ui.btn1 {
252                                    Calculator::number(&evt_ui, &evt_ui.btn1);
253                                } else if &handle == &evt_ui.btn2 {
254                                    Calculator::number(&evt_ui, &evt_ui.btn2);
255                                } else if &handle == &evt_ui.btn3 {
256                                    Calculator::number(&evt_ui, &evt_ui.btn3);
257                                } else if &handle == &evt_ui.btn4 {
258                                    Calculator::number(&evt_ui, &evt_ui.btn4);
259                                } else if &handle == &evt_ui.btn5 {
260                                    Calculator::number(&evt_ui, &evt_ui.btn5);
261                                } else if &handle == &evt_ui.btn6 {
262                                    Calculator::number(&evt_ui, &evt_ui.btn6);
263                                } else if &handle == &evt_ui.btn7 {
264                                    Calculator::number(&evt_ui, &evt_ui.btn7);
265                                } else if &handle == &evt_ui.btn8 {
266                                    Calculator::number(&evt_ui, &evt_ui.btn8);
267                                } else if &handle == &evt_ui.btn9 {
268                                    Calculator::number(&evt_ui, &evt_ui.btn9);
269                                } else if &handle == &evt_ui.btn_plus {
270                                    Calculator::number(&evt_ui, &evt_ui.btn_plus);
271                                } else if &handle == &evt_ui.btn_minus {
272                                    Calculator::number(&evt_ui, &evt_ui.btn_minus);
273                                } else if &handle == &evt_ui.btn_mult {
274                                    Calculator::number(&evt_ui, &evt_ui.btn_mult);
275                                } else if &handle == &evt_ui.btn_divide {
276                                    Calculator::number(&evt_ui, &evt_ui.btn_divide);
277                                } else if &handle == &evt_ui.btn_clear {
278                                    Calculator::clear(&evt_ui);
279                                } else if &handle == &evt_ui.btn_process {
280                                    Calculator::compute(&evt_ui);
281                                }
282                            }
283                            E::OnWindowClose => {
284                                if &handle == &evt_ui.window {
285                                    Calculator::exit(&evt_ui);
286                                }
287                            }
288                            _ => {}
289                        }
290                    }
291                };
292
293                ui.default_handler
294                    .borrow_mut()
295                    .push(nwg::full_bind_event_handler(handle, handle_events));
296            }
297
298            // Layouts
299            nwg::GridLayout::builder()
300                .parent(&ui.window)
301                .spacing(2)
302                .min_size([150, 140])
303                .child_item(nwg::GridLayoutItem::new(&ui.input, 0, 0, 5, 1))
304                .child(0, 1, &ui.btn1)
305                .child(1, 1, &ui.btn2)
306                .child(2, 1, &ui.btn3)
307                .child(0, 2, &ui.btn4)
308                .child(1, 2, &ui.btn5)
309                .child(2, 2, &ui.btn6)
310                .child(0, 3, &ui.btn7)
311                .child(1, 3, &ui.btn8)
312                .child(2, 3, &ui.btn9)
313                .child(3, 1, &ui.btn_plus)
314                .child(4, 1, &ui.btn_minus)
315                .child(3, 2, &ui.btn_mult)
316                .child(4, 2, &ui.btn_divide)
317                .child_item(nwg::GridLayoutItem::new(&ui.btn_clear, 3, 3, 2, 1))
318                .child_item(nwg::GridLayoutItem::new(&ui.btn_process, 3, 4, 2, 1))
319                .child_item(nwg::GridLayoutItem::new(&ui.btn0, 0, 4, 3, 1))
320                .build(&ui.layout)?;
321
322            return Ok(ui);
323        }
324    }
325
326    impl Drop for CalculatorUi {
327        /// To make sure that everything is freed without issues, the default handler must be unbound.
328        fn drop(&mut self) {
329            let mut handlers = self.default_handler.borrow_mut();
330            for handler in handlers.drain(0..) {
331                nwg::unbind_event_handler(&handler);
332            }
333        }
334    }
335
336    impl Deref for CalculatorUi {
337        type Target = Calculator;
338
339        fn deref(&self) -> &Calculator {
340            &self.inner
341        }
342    }
343}
344
345fn main() {
346    nwg::init().expect("Failed to init Native Windows GUI");
347    nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font");
348
349    let _app = Calculator::build_ui(Default::default()).expect("Failed to build UI");
350    nwg::dispatch_thread_events();
351}