calculator_mutable.rs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. //! This example showcases a simple calculator using an approach to state management where the state is composed of only
  2. //! a single signal. Since Dioxus implements traditional React diffing, state can be consolidated into a typical Rust struct
  3. //! with methods that take `&mut self`. For many use cases, this is a simple way to manage complex state without wrapping
  4. //! everything in a signal.
  5. //!
  6. //! Generally, you'll want to split your state into several signals if you have a large application, but for small
  7. //! applications, or focused components, this is a great way to manage state.
  8. use dioxus::desktop::tao::dpi::LogicalSize;
  9. use dioxus::desktop::{Config, WindowBuilder};
  10. use dioxus::html::input_data::keyboard_types::Key;
  11. use dioxus::html::MouseEvent;
  12. use dioxus::prelude::*;
  13. fn main() {
  14. LaunchBuilder::desktop()
  15. .with_cfg(
  16. Config::new().with_window(
  17. WindowBuilder::new()
  18. .with_title("Calculator Demo")
  19. .with_resizable(false)
  20. .with_inner_size(LogicalSize::new(320.0, 530.0)),
  21. ),
  22. )
  23. .launch(app);
  24. }
  25. const STYLE: &str = include_str!("./assets/calculator.css");
  26. fn app() -> Element {
  27. let mut state = use_signal(Calculator::new);
  28. rsx! {
  29. style { {STYLE} }
  30. div { id: "wrapper",
  31. div { class: "app",
  32. div {
  33. class: "calculator",
  34. onkeypress: move |evt| state.write().handle_keydown(evt),
  35. div { class: "calculator-display", {state.read().formatted_display()} }
  36. div { class: "calculator-keypad",
  37. div { class: "input-keys",
  38. div { class: "function-keys",
  39. CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(),
  40. if state.read().display_value == "0" { "C" } else { "AC" }
  41. }
  42. CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±" }
  43. CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%" }
  44. }
  45. div { class: "digit-keys",
  46. CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
  47. CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" }
  48. for k in 1..10 {
  49. CalculatorKey {
  50. key: "{k}",
  51. name: "key-{k}",
  52. onclick: move |_| state.write().input_digit(k),
  53. "{k}"
  54. }
  55. }
  56. }
  57. }
  58. div { class: "operator-keys",
  59. CalculatorKey {
  60. name: "key-divide",
  61. onclick: move |_| state.write().set_operator(Operator::Div),
  62. "÷"
  63. }
  64. CalculatorKey {
  65. name: "key-multiply",
  66. onclick: move |_| state.write().set_operator(Operator::Mul),
  67. "×"
  68. }
  69. CalculatorKey {
  70. name: "key-subtract",
  71. onclick: move |_| state.write().set_operator(Operator::Sub),
  72. "−"
  73. }
  74. CalculatorKey { name: "key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" }
  75. CalculatorKey { name: "key-equals", onclick: move |_| state.write().perform_operation(), "=" }
  76. }
  77. }
  78. }
  79. }
  80. }
  81. }
  82. }
  83. #[component]
  84. fn CalculatorKey(name: String, onclick: EventHandler<MouseEvent>, children: Element) -> Element {
  85. rsx! {
  86. button { class: "calculator-key {name}", onclick: move |e| onclick.call(e), {&children} }
  87. }
  88. }
  89. struct Calculator {
  90. display_value: String,
  91. operator: Option<Operator>,
  92. waiting_for_operand: bool,
  93. cur_val: f64,
  94. }
  95. #[derive(Clone)]
  96. enum Operator {
  97. Add,
  98. Sub,
  99. Mul,
  100. Div,
  101. }
  102. impl Calculator {
  103. fn new() -> Self {
  104. Calculator {
  105. display_value: "0".to_string(),
  106. operator: None,
  107. waiting_for_operand: false,
  108. cur_val: 0.0,
  109. }
  110. }
  111. fn formatted_display(&self) -> String {
  112. use separator::Separatable;
  113. self.display_value
  114. .parse::<f64>()
  115. .unwrap()
  116. .separated_string()
  117. }
  118. fn clear_display(&mut self) {
  119. self.display_value = "0".to_string();
  120. }
  121. fn input_digit(&mut self, digit: u8) {
  122. let content = digit.to_string();
  123. if self.waiting_for_operand || self.display_value == "0" {
  124. self.waiting_for_operand = false;
  125. self.display_value = content;
  126. } else {
  127. self.display_value.push_str(content.as_str());
  128. }
  129. }
  130. fn input_dot(&mut self) {
  131. if !self.display_value.contains('.') {
  132. self.display_value.push('.');
  133. }
  134. }
  135. fn perform_operation(&mut self) {
  136. if let Some(op) = &self.operator {
  137. let rhs = self.display_value.parse::<f64>().unwrap();
  138. let new_val = match op {
  139. Operator::Add => self.cur_val + rhs,
  140. Operator::Sub => self.cur_val - rhs,
  141. Operator::Mul => self.cur_val * rhs,
  142. Operator::Div => self.cur_val / rhs,
  143. };
  144. self.cur_val = new_val;
  145. self.display_value = new_val.to_string();
  146. self.operator = None;
  147. }
  148. }
  149. fn toggle_sign(&mut self) {
  150. if self.display_value.starts_with('-') {
  151. self.display_value = self.display_value.trim_start_matches('-').to_string();
  152. } else {
  153. self.display_value = format!("-{}", self.display_value);
  154. }
  155. }
  156. fn toggle_percent(&mut self) {
  157. self.display_value = (self.display_value.parse::<f64>().unwrap() / 100.0).to_string();
  158. }
  159. fn backspace(&mut self) {
  160. if !self.display_value.as_str().eq("0") {
  161. self.display_value.pop();
  162. }
  163. }
  164. fn set_operator(&mut self, operator: Operator) {
  165. self.operator = Some(operator);
  166. self.cur_val = self.display_value.parse::<f64>().unwrap();
  167. self.waiting_for_operand = true;
  168. }
  169. fn handle_keydown(&mut self, evt: KeyboardEvent) {
  170. match evt.key() {
  171. Key::Backspace => self.backspace(),
  172. Key::Character(c) => match c.as_str() {
  173. "0" => self.input_digit(0),
  174. "1" => self.input_digit(1),
  175. "2" => self.input_digit(2),
  176. "3" => self.input_digit(3),
  177. "4" => self.input_digit(4),
  178. "5" => self.input_digit(5),
  179. "6" => self.input_digit(6),
  180. "7" => self.input_digit(7),
  181. "8" => self.input_digit(8),
  182. "9" => self.input_digit(9),
  183. "+" => self.operator = Some(Operator::Add),
  184. "-" => self.operator = Some(Operator::Sub),
  185. "/" => self.operator = Some(Operator::Div),
  186. "*" => self.operator = Some(Operator::Mul),
  187. _ => {}
  188. },
  189. _ => {}
  190. }
  191. }
  192. }