Skip to main content

calculator/
lib.rs

1//! A simple calculator. Only components of the library and its tokens, in about a hundred
2//! lines, so it doubles as a small and steady screen to compare performance with: the
3//! scene `calculator` in `crates/ravenrs-perf` measures this very view.
4
5use std::rc::Rc;
6
7use ravenrs::core::Key;
8use ravenrs::prelude::*;
9
10pub mod engine;
11
12use engine::{Engine, Op, Press};
13
14/// The buttons, row by row: what they show, what they do and how they look.
15const KEYS: [(&str, &str, Press, ButtonStyle); 20] = [
16    ("C", "Clear", Press::Clear, ButtonStyle::Ghost),
17    ("⌫", "Erase", Press::Backspace, ButtonStyle::Ghost),
18    ("%", "Percent", Press::Percent, ButtonStyle::Ghost),
19    ("÷", "Divide", Press::Op(Op::Divide), ButtonStyle::Secondary),
20    ("7", "7", Press::Digit(7), ButtonStyle::Secondary),
21    ("8", "8", Press::Digit(8), ButtonStyle::Secondary),
22    ("9", "9", Press::Digit(9), ButtonStyle::Secondary),
23    ("×", "Multiply", Press::Op(Op::Multiply), ButtonStyle::Secondary),
24    ("4", "4", Press::Digit(4), ButtonStyle::Secondary),
25    ("5", "5", Press::Digit(5), ButtonStyle::Secondary),
26    ("6", "6", Press::Digit(6), ButtonStyle::Secondary),
27    ("−", "Subtract", Press::Op(Op::Subtract), ButtonStyle::Secondary),
28    ("1", "1", Press::Digit(1), ButtonStyle::Secondary),
29    ("2", "2", Press::Digit(2), ButtonStyle::Secondary),
30    ("3", "3", Press::Digit(3), ButtonStyle::Secondary),
31    ("+", "Add", Press::Op(Op::Add), ButtonStyle::Secondary),
32    ("±", "Change sign", Press::Negate, ButtonStyle::Secondary),
33    ("0", "0", Press::Digit(0), ButtonStyle::Secondary),
34    (".", "Point", Press::Point, ButtonStyle::Secondary),
35    ("=", "Equals", Press::Equals, ButtonStyle::Primary),
36];
37
38/// The keyboard: what each key on it presses.
39fn keyboard() -> Vec<(Key, Press)> {
40    let character = |text: &str| Key::Character(text.into());
41    let mut keys: Vec<(Key, Press)> =
42        (0..=9).map(|digit| (character(&digit.to_string()), Press::Digit(digit))).collect();
43    keys.extend([
44        (character("."), Press::Point),
45        (character(","), Press::Point),
46        (character("+"), Press::Op(Op::Add)),
47        (character("-"), Press::Op(Op::Subtract)),
48        (character("*"), Press::Op(Op::Multiply)),
49        (character("/"), Press::Op(Op::Divide)),
50        (character("%"), Press::Percent),
51        (character("="), Press::Equals),
52        (Key::Enter, Press::Equals),
53        (Key::Backspace, Press::Backspace),
54        (Key::Escape, Press::Clear),
55    ]);
56    keys
57}
58
59/// The calculator.
60#[derive(Debug, Default)]
61pub struct Calculator {
62    engine: State<Engine>,
63}
64
65impl View for Calculator {
66    fn build(&self, cx: &mut BuildCx<'_>) -> NodeId {
67        for (key, press) in keyboard() {
68            let engine = self.engine.clone();
69            cx.shortcut(key, false, Rc::new(move || engine.update(|engine| engine.press(press))));
70        }
71        let keys: Vec<AnyView> = KEYS
72            .iter()
73            .map(|&(label, name, press, style)| -> AnyView {
74                let engine = self.engine.clone();
75                Box::new(
76                    Button::new(label)
77                        .accessible_name(name)
78                        .style(style)
79                        .size(ButtonSize::Large)
80                        .full_width()
81                        .on_click(move || engine.update(|engine| engine.press(press))),
82                )
83            })
84            .collect();
85        cx.child(
86            &VStack::new((
87                VStack::new((Text::bound(&self.engine, |engine| engine.display().to_owned())
88                    .style(TextStyle::Metric)
89                    .truncate(),))
90                .align(Align::End),
91                Grid::new(4, keys).spacing(8.0),
92            ))
93            .align(Align::Stretch)
94            .spacing(16.0)
95            .padding(16.0)
96            .grow(1.0),
97        )
98    }
99}
100
101/// The app.
102pub fn app() -> Calculator {
103    Calculator::default()
104}
105
106#[cfg(target_arch = "wasm32")]
107mod web_entry {
108    use std::cell::RefCell;
109
110    use wasm_bindgen::prelude::*;
111
112    thread_local! {
113        static APP: RefCell<Option<ravenrs::web::WebApp>> = const { RefCell::new(None) };
114    }
115
116    /// Runs automatically when the module is instantiated.
117    #[wasm_bindgen(start)]
118    pub fn start() {
119        console_error_panic_hook::set_once();
120        wasm_bindgen_futures::spawn_local(async {
121            match ravenrs::web::start(super::app(), ravenrs::web::WebOptions::new("raven-canvas")).await {
122                Ok(app) => APP.with(|a| *a.borrow_mut() = Some(app)),
123                Err(e) => web_sys::console::error_1(&e.to_string().into()),
124            }
125        });
126    }
127}