calculator_mutable.rs 8.7 KB

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