calculator/
calculator.rs

1use std::fmt::Display;
2
3use ori::prelude::*;
4
5#[derive(Clone, Copy, Debug, Default)]
6struct Number {
7    value: f64,
8    position: Option<i8>,
9}
10
11impl Number {
12    fn new(value: f64) -> Self {
13        Self {
14            value,
15            position: None,
16        }
17    }
18
19    fn add_digit(&mut self, digit: u8) {
20        let Some(position) = self.position else {
21            self.position = Some(1);
22            self.value = digit as f64;
23            return;
24        };
25
26        let sign = self.value.signum();
27
28        if position < 0 {
29            let pow = 10.0f64.powi(position as i32);
30            self.value += digit as f64 * pow * sign;
31            self.position = Some(position - 1);
32        } else {
33            self.value *= 10.0;
34            self.value += digit as f64 * sign;
35            self.position = Some(position + 1);
36        }
37    }
38
39    fn remove_digit(&mut self) {
40        let Some(position) = self.position else {
41            self.position = Some(0);
42            self.value = 0.0;
43            return;
44        };
45
46        if position < -1 {
47            self.value *= 10.0f64.powi(-position as i32 - 2);
48            self.value = self.value.trunc();
49            self.value /= 10.0f64.powi(-position as i32 - 2);
50
51            if position == -2 {
52                self.position = Some(0);
53            } else {
54                self.position = Some(position + 1);
55            }
56        } else if position >= 0 {
57            self.value /= 10.0;
58            self.value = self.value.trunc();
59
60            self.position = Some((position - 1).max(0));
61        } else {
62            self.position = Some(0);
63        }
64
65        // ensure that -0.0 is not displayed
66        if self.value == -0.0 {
67            self.value = 0.0;
68        }
69    }
70}
71
72impl Display for Number {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        let Some(position) = self.position else {
75            return write!(f, "{}", self.value);
76        };
77
78        if position == -1 {
79            write!(f, "{}.", self.value)
80        } else if position < 0 {
81            write!(f, "{:.1$}", self.value, -position as usize - 1)
82        } else {
83            write!(f, "{}", self.value)
84        }
85    }
86}
87
88#[derive(Clone, Copy, Debug)]
89enum Operator {
90    None,
91    Add,
92    Subtract,
93    Multiply,
94    Divide,
95}
96
97fn result_bar<'a>(
98    cx: Scope<'a>,
99    operator: &'a Signal<Operator>,
100    result: &'a Signal<Number>,
101    rhs: &'a Signal<Number>,
102) -> impl View {
103    let text = cx.memo(|| {
104        let result = result.get();
105        let operator = operator.get();
106        let rhs = rhs.get();
107
108        match *operator {
109            Operator::None => format!("{}", result),
110            Operator::Add => format!("{} + {}", result, rhs),
111            Operator::Subtract => format!("{} - {}", result, rhs),
112            Operator::Multiply => format!("{} × {}", result, rhs),
113            Operator::Divide => format!("{} ÷ {}", result, rhs),
114        }
115    });
116
117    view! {
118        <Div class="result-bar">
119            <Text class="result" text=text.cloned() />
120        </Div>
121    }
122}
123
124fn bar0<'a>(
125    cx: Scope<'a>,
126    operator: &'a Signal<Operator>,
127    result: &'a Signal<Number>,
128    rhs: &'a Signal<Number>,
129) -> impl View {
130    let clear_all = |_: &PointerEvent| {
131        operator.set(Operator::None);
132        result.set(Number::new(0.0));
133        rhs.set(Number::new(0.0));
134    };
135
136    let clear = |_: &PointerEvent| {
137        if matches!(*operator.get(), Operator::None) {
138            result.set(Number::new(0.0));
139        } else {
140            rhs.set(Number::new(0.0));
141        }
142    };
143
144    let remove_digit = |_: &PointerEvent| {
145        if matches!(*operator.get(), Operator::None) {
146            result.modify().remove_digit();
147        } else {
148            rhs.modify().remove_digit();
149        }
150    };
151
152    let divide = |_: &PointerEvent| {
153        operator.set(Operator::Divide);
154    };
155
156    view! {
157        <Div class="buttons row">
158            <Button on:press=clear_all>
159                <Text text="CE" />
160            </Button>
161            <Button on:press=clear>
162                <Text text="C" />
163            </Button>
164            <Button on:press=remove_digit>
165                <Text text="\u{e14a}" style:font="icon" />
166            </Button>
167            <Button on:press=divide>
168                <Text text="÷" />
169            </Button>
170        </Div>
171    }
172}
173
174fn add_digit<'a>(
175    operator: &'a Signal<Operator>,
176    result: &'a Signal<Number>,
177    rhs: &'a Signal<Number>,
178    digit: u8,
179) -> impl Fn(&PointerEvent) + 'a {
180    move |_| {
181        if matches!(*operator.get(), Operator::None) {
182            result.modify().add_digit(digit);
183        } else {
184            rhs.modify().add_digit(digit);
185        }
186    }
187}
188
189fn bar1<'a>(
190    cx: Scope<'a>,
191    operator: &'a Signal<Operator>,
192    result: &'a Signal<Number>,
193    rhs: &'a Signal<Number>,
194) -> impl View {
195    let multiply = |_: &PointerEvent| {
196        operator.set(Operator::Multiply);
197    };
198
199    view! {
200        <Div class="buttons row">
201            <Button class="number" on:press=add_digit(operator, result, rhs, 7)>
202                <Text text="7" />
203            </Button>
204            <Button class="number" on:press=add_digit(operator, result, rhs, 8)>
205                <Text text="8" />
206            </Button>
207            <Button class="number" on:press=add_digit(operator, result, rhs, 9)>
208                <Text text="9" />
209            </Button>
210            <Button on:press=multiply>
211                <Text text="×" />
212            </Button>
213        </Div>
214    }
215}
216
217fn bar2<'a>(
218    cx: Scope<'a>,
219    operator: &'a Signal<Operator>,
220    result: &'a Signal<Number>,
221    rhs: &'a Signal<Number>,
222) -> impl View {
223    let subtract = |_: &PointerEvent| {
224        operator.set(Operator::Subtract);
225    };
226
227    view! {
228        <Div class="buttons row">
229            <Button class="number" on:press=add_digit(operator, result, rhs, 4)>
230                <Text text="4" />
231            </Button>
232            <Button class="number" on:press=add_digit(operator, result, rhs, 5)>
233                <Text text="5" />
234            </Button>
235            <Button class="number" on:press=add_digit(operator, result, rhs, 6)>
236                <Text text="6" />
237            </Button>
238            <Button on:press=subtract>
239                <Text text="-" />
240            </Button>
241        </Div>
242    }
243}
244
245fn bar3<'a>(
246    cx: Scope<'a>,
247    operator: &'a Signal<Operator>,
248    result: &'a Signal<Number>,
249    rhs: &'a Signal<Number>,
250) -> impl View {
251    let add = |_: &PointerEvent| {
252        operator.set(Operator::Add);
253    };
254
255    view! {
256        <Div class="buttons row">
257            <Button class="number" on:press=add_digit(operator, result, rhs, 1)>
258                <Text text="1" />
259            </Button>
260            <Button class="number" on:press=add_digit(operator, result, rhs, 2)>
261                <Text text="2" />
262            </Button>
263            <Button class="number" on:press=add_digit(operator, result, rhs, 3)>
264                <Text text="3" />
265            </Button>
266            <Button on:press=add>
267                <Text text="+" />
268            </Button>
269        </Div>
270    }
271}
272
273fn bar4<'a>(
274    cx: Scope<'a>,
275    operator: &'a Signal<Operator>,
276    result: &'a Signal<Number>,
277    rhs: &'a Signal<Number>,
278) -> impl View {
279    let negate = |_: &PointerEvent| {
280        if result.get().value == 0.0 {
281            return;
282        }
283
284        if matches!(*operator.get(), Operator::None) {
285            result.modify().value *= -1.0;
286        } else {
287            rhs.modify().value *= -1.0;
288        }
289    };
290
291    let add_point = |_: &PointerEvent| {
292        if let Some(position) = result.get().position {
293            if position < 0 {
294                return;
295            }
296        }
297
298        if matches!(*operator.get(), Operator::None) {
299            result.modify().position = Some(-1);
300        } else {
301            rhs.modify().position = Some(-1);
302        }
303    };
304
305    let equals = |_: &PointerEvent| {
306        let mut result = result.modify();
307        let mut rhs = rhs.modify();
308        let mut operator = operator.modify();
309        match *operator {
310            Operator::None => {}
311            Operator::Add => {
312                *result = Number::new(result.value + rhs.value);
313            }
314            Operator::Subtract => {
315                *result = Number::new(result.value - rhs.value);
316            }
317            Operator::Multiply => {
318                *result = Number::new(result.value * rhs.value);
319            }
320            Operator::Divide => {
321                *result = Number::new(result.value / rhs.value);
322            }
323        }
324        *operator = Operator::None;
325        *rhs = Number::new(0.0);
326    };
327
328    view! {
329        <Div class="buttons row">
330            <Button on:press=negate>
331                <Text text="±" />
332            </Button>
333            <Button class="number" on:press=add_digit(operator, result, rhs, 0)>
334                <Text text="0" />
335            </Button>
336            <Button on:press=add_point>
337                <Text text="." />
338            </Button>
339            <Button on:press=equals>
340                <Text text="=" />
341            </Button>
342        </Div>
343    }
344}
345
346fn buttons<'a>(
347    cx: Scope<'a>,
348    operator: &'a Signal<Operator>,
349    result: &'a Signal<Number>,
350    rhs: &'a Signal<Number>,
351) -> impl View {
352    view! {
353        <Div class="buttons column">
354            { bar0(cx, operator, result, rhs) }
355            { bar1(cx, operator, result, rhs) }
356            { bar2(cx, operator, result, rhs) }
357            { bar3(cx, operator, result, rhs) }
358            { bar4(cx, operator, result, rhs) }
359        </Div>
360    }
361}
362
363fn ui(cx: Scope) -> impl View {
364    let operator = cx.signal(Operator::None);
365    let result = cx.signal(Number::new(0.0));
366    let rhs = cx.signal(Number::new(0.0));
367
368    view! {
369        { result_bar(cx, operator, result, rhs) }
370        { buttons(cx, operator, result, rhs) }
371    }
372}
373
374fn main() {
375    App::new(ui)
376        .title("Calculator (examples/calculator.rs)")
377        .style(include_stylesheet!("style/calculator.css"))
378        .reziseable(false)
379        .transparent()
380        .size(300.0, 400.0)
381        .run();
382}