calculator_mutable.rs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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. dioxus::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. fn app() -> Element {
  26. let mut state = use_signal(Calculator::new);
  27. rsx! {
  28. document::Link { rel: "stylesheet", href: asset!("./examples/assets/calculator.css") }
  29. div { id: "wrapper",
  30. div { class: "app",
  31. div {
  32. class: "calculator",
  33. onkeypress: move |evt| state.write().handle_keydown(evt),
  34. div { class: "calculator-display", {state.read().formatted_display()} }
  35. div { class: "calculator-keypad",
  36. div { class: "input-keys",
  37. div { class: "function-keys",
  38. CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(),
  39. if state.read().display_value == "0" { "C" } else { "AC" }
  40. }
  41. CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±" }
  42. CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%" }
  43. }
  44. div { class: "digit-keys",
  45. CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
  46. CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" }
  47. for k in 1..10 {
  48. CalculatorKey {
  49. key: "{k}",
  50. name: "key-{k}",
  51. onclick: move |_| state.write().input_digit(k),
  52. "{k}"
  53. }
  54. }
  55. }
  56. }
  57. div { class: "operator-keys",
  58. CalculatorKey {
  59. name: "key-divide",
  60. onclick: move |_| state.write().set_operator(Operator::Div),
  61. "÷"
  62. }
  63. CalculatorKey {
  64. name: "key-multiply",
  65. onclick: move |_| state.write().set_operator(Operator::Mul),
  66. "×"
  67. }
  68. CalculatorKey {
  69. name: "key-subtract",
  70. onclick: move |_| state.write().set_operator(Operator::Sub),
  71. "−"
  72. }
  73. CalculatorKey { name: "key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" }
  74. CalculatorKey { name: "key-equals", onclick: move |_| state.write().perform_operation(), "=" }
  75. }
  76. }
  77. }
  78. }
  79. }
  80. }
  81. }
  82. #[component]
  83. fn CalculatorKey(name: String, onclick: EventHandler<MouseEvent>, children: Element) -> Element {
  84. rsx! {
  85. button { class: "calculator-key {name}", onclick, {children} }
  86. }
  87. }
  88. struct Calculator {
  89. display_value: String,
  90. operator: Option<Operator>,
  91. waiting_for_operand: bool,
  92. cur_val: f64,
  93. }
  94. #[derive(Clone)]
  95. enum Operator {
  96. Add,
  97. Sub,
  98. Mul,
  99. Div,
  100. }
  101. impl Calculator {
  102. fn new() -> Self {
  103. Calculator {
  104. display_value: "0".to_string(),
  105. operator: None,
  106. waiting_for_operand: false,
  107. cur_val: 0.0,
  108. }
  109. }
  110. fn formatted_display(&self) -> String {
  111. use separator::Separatable;
  112. self.display_value
  113. .parse::<f64>()
  114. .unwrap()
  115. .separated_string()
  116. }
  117. fn clear_display(&mut self) {
  118. self.display_value = "0".to_string();
  119. }
  120. fn input_digit(&mut self, digit: u8) {
  121. let content = digit.to_string();
  122. if self.waiting_for_operand || self.display_value == "0" {
  123. self.waiting_for_operand = false;
  124. self.display_value = content;
  125. } else {
  126. self.display_value.push_str(content.as_str());
  127. }
  128. }
  129. fn input_dot(&mut self) {
  130. if !self.display_value.contains('.') {
  131. self.display_value.push('.');
  132. }
  133. }
  134. fn perform_operation(&mut self) {
  135. if let Some(op) = &self.operator {
  136. let rhs = self.display_value.parse::<f64>().unwrap();
  137. let new_val = match op {
  138. Operator::Add => self.cur_val + rhs,
  139. Operator::Sub => self.cur_val - rhs,
  140. Operator::Mul => self.cur_val * rhs,
  141. Operator::Div => self.cur_val / rhs,
  142. };
  143. self.cur_val = new_val;
  144. self.display_value = new_val.to_string();
  145. self.operator = None;
  146. }
  147. }
  148. fn toggle_sign(&mut self) {
  149. if self.display_value.starts_with('-') {
  150. self.display_value = self.display_value.trim_start_matches('-').to_string();
  151. } else {
  152. self.display_value = format!("-{}", self.display_value);
  153. }
  154. }
  155. fn toggle_percent(&mut self) {
  156. self.display_value = (self.display_value.parse::<f64>().unwrap() / 100.0).to_string();
  157. }
  158. fn backspace(&mut self) {
  159. if !self.display_value.as_str().eq("0") {
  160. self.display_value.pop();
  161. }
  162. }
  163. fn set_operator(&mut self, operator: Operator) {
  164. self.operator = Some(operator);
  165. self.cur_val = self.display_value.parse::<f64>().unwrap();
  166. self.waiting_for_operand = true;
  167. }
  168. fn handle_keydown(&mut self, evt: KeyboardEvent) {
  169. match evt.key() {
  170. Key::Backspace => self.backspace(),
  171. Key::Character(c) => match c.as_str() {
  172. "0" => self.input_digit(0),
  173. "1" => self.input_digit(1),
  174. "2" => self.input_digit(2),
  175. "3" => self.input_digit(3),
  176. "4" => self.input_digit(4),
  177. "5" => self.input_digit(5),
  178. "6" => self.input_digit(6),
  179. "7" => self.input_digit(7),
  180. "8" => self.input_digit(8),
  181. "9" => self.input_digit(9),
  182. "+" => self.operator = Some(Operator::Add),
  183. "-" => self.operator = Some(Operator::Sub),
  184. "/" => self.operator = Some(Operator::Div),
  185. "*" => self.operator = Some(Operator::Mul),
  186. _ => {}
  187. },
  188. _ => {}
  189. }
  190. }
  191. }