pattern_model.rs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #![allow(non_snake_case)]
  2. //! Example: Calculator
  3. //! -------------------
  4. //!
  5. //! Some components benefit through the use of "Models". Models are a single block of encapsulated state that allow mutative
  6. //! methods to be performed on them. Dioxus exposes the ability to use the model pattern through the "use_model" hook.
  7. //!
  8. //! Models are commonly used in the "Model-View-Component" approach for building UI state.
  9. //!
  10. //! `use_model` is basically just a fancy wrapper around set_state, but saves a "working copy" of the new state behind a
  11. //! RefCell. To modify the working copy, you need to call "get_mut" which returns the RefMut. This makes it easy to write
  12. //! fully encapsulated apps that retain a certain feel of native Rusty-ness. A calculator app is a good example of when this
  13. //! is useful.
  14. //!
  15. //! Do note that "get_mut" returns a `RefMut` (a lock over a RefCell). If two `RefMut`s are held at the same time (ie in a loop)
  16. //! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
  17. //! RefMuts at the same time.
  18. use dioxus::events::*;
  19. use dioxus::html::input_data::keyboard_types::Key;
  20. use dioxus::html::MouseEvent;
  21. use dioxus::prelude::*;
  22. use dioxus_desktop::tao::dpi::LogicalSize;
  23. use dioxus_desktop::{Config, WindowBuilder};
  24. fn main() {
  25. let cfg = Config::new().with_window(
  26. WindowBuilder::new()
  27. .with_title("Calculator Demo")
  28. .with_resizable(false)
  29. .with_inner_size(LogicalSize::new(320.0, 530.0)),
  30. );
  31. LaunchBuilder::new().with_cfg(cfg).launch(app);
  32. }
  33. const STYLE: &str = include_str!("./assets/calculator.css");
  34. fn app() -> Element {
  35. let mut state = use_signal(Calculator::new);
  36. rsx! {
  37. style { {STYLE} }
  38. div { id: "wrapper",
  39. div { class: "app",
  40. div {
  41. class: "calculator",
  42. onkeypress: move |evt| state.write().handle_keydown(evt),
  43. div { class: "calculator-display", {state.read().formatted_display()} }
  44. div { class: "calculator-keypad",
  45. div { class: "input-keys",
  46. div { class: "function-keys",
  47. CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(),
  48. if state.read().display_value == "0" { "C" } else { "AC" }
  49. }
  50. CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±" }
  51. CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%" }
  52. }
  53. div { class: "digit-keys",
  54. CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
  55. CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" }
  56. for k in 1..10 {
  57. CalculatorKey {
  58. key: "{k}",
  59. name: "key-{k}",
  60. onclick: move |_| state.write().input_digit(k),
  61. "{k}"
  62. }
  63. }
  64. }
  65. }
  66. div { class: "operator-keys",
  67. CalculatorKey {
  68. name: "key-divide",
  69. onclick: move |_| state.write().set_operator(Operator::Div),
  70. "÷"
  71. }
  72. CalculatorKey {
  73. name: "key-multiply",
  74. onclick: move |_| state.write().set_operator(Operator::Mul),
  75. "×"
  76. }
  77. CalculatorKey {
  78. name: "key-subtract",
  79. onclick: move |_| state.write().set_operator(Operator::Sub),
  80. "−"
  81. }
  82. CalculatorKey { name: "key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" }
  83. CalculatorKey { name: "key-equals", onclick: move |_| state.write().perform_operation(), "=" }
  84. }
  85. }
  86. }
  87. }
  88. }
  89. }
  90. }
  91. #[component]
  92. fn CalculatorKey(name: String, onclick: EventHandler<MouseEvent>, children: Element) -> Element {
  93. rsx! {
  94. button { class: "calculator-key {name}", onclick: move |e| onclick.call(e), {&children} }
  95. }
  96. }
  97. struct Calculator {
  98. display_value: String,
  99. operator: Option<Operator>,
  100. waiting_for_operand: bool,
  101. cur_val: f64,
  102. }
  103. #[derive(Clone)]
  104. enum Operator {
  105. Add,
  106. Sub,
  107. Mul,
  108. Div,
  109. }
  110. impl Calculator {
  111. fn new() -> Self {
  112. Calculator {
  113. display_value: "0".to_string(),
  114. operator: None,
  115. waiting_for_operand: false,
  116. cur_val: 0.0,
  117. }
  118. }
  119. fn formatted_display(&self) -> String {
  120. use separator::Separatable;
  121. self.display_value
  122. .parse::<f64>()
  123. .unwrap()
  124. .separated_string()
  125. }
  126. fn clear_display(&mut self) {
  127. self.display_value = "0".to_string();
  128. }
  129. fn input_digit(&mut self, digit: u8) {
  130. let content = digit.to_string();
  131. if self.waiting_for_operand || self.display_value == "0" {
  132. self.waiting_for_operand = false;
  133. self.display_value = content;
  134. } else {
  135. self.display_value.push_str(content.as_str());
  136. }
  137. }
  138. fn input_dot(&mut self) {
  139. if !self.display_value.contains('.') {
  140. self.display_value.push('.');
  141. }
  142. }
  143. fn perform_operation(&mut self) {
  144. if let Some(op) = &self.operator {
  145. let rhs = self.display_value.parse::<f64>().unwrap();
  146. let new_val = match op {
  147. Operator::Add => self.cur_val + rhs,
  148. Operator::Sub => self.cur_val - rhs,
  149. Operator::Mul => self.cur_val * rhs,
  150. Operator::Div => self.cur_val / rhs,
  151. };
  152. self.cur_val = new_val;
  153. self.display_value = new_val.to_string();
  154. self.operator = None;
  155. }
  156. }
  157. fn toggle_sign(&mut self) {
  158. if self.display_value.starts_with('-') {
  159. self.display_value = self.display_value.trim_start_matches('-').to_string();
  160. } else {
  161. self.display_value = format!("-{}", self.display_value);
  162. }
  163. }
  164. fn toggle_percent(&mut self) {
  165. self.display_value = (self.display_value.parse::<f64>().unwrap() / 100.0).to_string();
  166. }
  167. fn backspace(&mut self) {
  168. if !self.display_value.as_str().eq("0") {
  169. self.display_value.pop();
  170. }
  171. }
  172. fn set_operator(&mut self, operator: Operator) {
  173. self.operator = Some(operator);
  174. self.cur_val = self.display_value.parse::<f64>().unwrap();
  175. self.waiting_for_operand = true;
  176. }
  177. fn handle_keydown(&mut self, evt: KeyboardEvent) {
  178. match evt.key() {
  179. Key::Backspace => self.backspace(),
  180. Key::Character(c) => match c.as_str() {
  181. "0" => self.input_digit(0),
  182. "1" => self.input_digit(1),
  183. "2" => self.input_digit(2),
  184. "3" => self.input_digit(3),
  185. "4" => self.input_digit(4),
  186. "5" => self.input_digit(5),
  187. "6" => self.input_digit(6),
  188. "7" => self.input_digit(7),
  189. "8" => self.input_digit(8),
  190. "9" => self.input_digit(9),
  191. "+" => self.operator = Some(Operator::Add),
  192. "-" => self.operator = Some(Operator::Sub),
  193. "/" => self.operator = Some(Operator::Div),
  194. "*" => self.operator = Some(Operator::Mul),
  195. _ => {}
  196. },
  197. _ => {}
  198. }
  199. }
  200. }