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 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}