number.rs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. use crate::widgets::get_root_id;
  2. use crate::Query;
  3. use crossterm::{cursor::MoveTo, execute};
  4. use dioxus::prelude::*;
  5. use dioxus_elements::input_data::keyboard_types::Key;
  6. use dioxus_html as dioxus_elements;
  7. use dioxus_html::FormData;
  8. use dioxus_native_core::utils::cursor::{Cursor, Pos};
  9. use std::{collections::HashMap, io::stdout};
  10. use taffy::geometry::Point;
  11. #[derive(Props)]
  12. pub(crate) struct NumbericInputProps<'a> {
  13. #[props(!optional)]
  14. raw_oninput: Option<&'a EventHandler<'a, FormData>>,
  15. #[props(!optional)]
  16. value: Option<&'a str>,
  17. #[props(!optional)]
  18. size: Option<&'a str>,
  19. #[props(!optional)]
  20. max_length: Option<&'a str>,
  21. #[props(!optional)]
  22. width: Option<&'a str>,
  23. #[props(!optional)]
  24. height: Option<&'a str>,
  25. }
  26. #[allow(non_snake_case)]
  27. pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a> {
  28. let tui_query: Query = cx.consume_context().unwrap();
  29. let tui_query_clone = tui_query.clone();
  30. let text_ref = use_ref(cx, || {
  31. if let Some(intial_text) = cx.props.value {
  32. intial_text.to_string()
  33. } else {
  34. String::new()
  35. }
  36. });
  37. let cursor = use_ref(cx, Cursor::default);
  38. let dragging = use_state(cx, || false);
  39. let text = text_ref.read().clone();
  40. let start_highlight = cursor.read().first().idx(&text);
  41. let end_highlight = cursor.read().last().idx(&text);
  42. let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
  43. let (text_highlighted, text_after_second_cursor) =
  44. text_after_first_cursor.split_at(end_highlight - start_highlight);
  45. let max_len = cx
  46. .props
  47. .max_length
  48. .as_ref()
  49. .and_then(|s| s.parse().ok())
  50. .unwrap_or(usize::MAX);
  51. let width = cx
  52. .props
  53. .width
  54. .map(|s| s.to_string())
  55. // px is the same as em in tui
  56. .or_else(|| cx.props.size.map(|s| s.to_string() + "px"))
  57. .unwrap_or_else(|| "10px".to_string());
  58. let height = cx.props.height.unwrap_or("3px");
  59. // don't draw a border unless there is enough space
  60. let border = if width
  61. .strip_suffix("px")
  62. .and_then(|w| w.parse::<i32>().ok())
  63. .filter(|w| *w < 3)
  64. .is_some()
  65. || height
  66. .strip_suffix("px")
  67. .and_then(|h| h.parse::<i32>().ok())
  68. .filter(|h| *h < 3)
  69. .is_some()
  70. {
  71. "none"
  72. } else {
  73. "solid"
  74. };
  75. let update = |text: String| {
  76. if let Some(input_handler) = &cx.props.raw_oninput {
  77. input_handler.call(FormData {
  78. value: text,
  79. values: HashMap::new(),
  80. });
  81. }
  82. };
  83. let increase = move || {
  84. let mut text = text_ref.write();
  85. *text = (text.parse::<f64>().unwrap_or(0.0) + 1.0).to_string();
  86. update(text.clone());
  87. };
  88. let decrease = move || {
  89. let mut text = text_ref.write();
  90. *text = (text.parse::<f64>().unwrap_or(0.0) - 1.0).to_string();
  91. update(text.clone());
  92. };
  93. render! {
  94. div{
  95. width: "{width}",
  96. height: "{height}",
  97. border_style: "{border}",
  98. onkeydown: move |k| {
  99. let is_text = match k.key(){
  100. Key::ArrowLeft | Key::ArrowRight | Key::Backspace => true,
  101. Key::Character(c) if c=="." || c== "-" || c.chars().all(|c|c.is_numeric())=> true,
  102. _ => false,
  103. };
  104. if is_text{
  105. let mut text = text_ref.write();
  106. cursor.write().handle_input(&k, &mut text, max_len);
  107. update(text.clone());
  108. let node = tui_query.get(get_root_id(cx).unwrap());
  109. let Point{ x, y } = node.pos().unwrap();
  110. let Pos { col, row } = cursor.read().start;
  111. let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
  112. if let Ok(pos) = crossterm::cursor::position() {
  113. if pos != (x, y){
  114. execute!(stdout(), MoveTo(x, y)).unwrap();
  115. }
  116. }
  117. else{
  118. execute!(stdout(), MoveTo(x, y)).unwrap();
  119. }
  120. }
  121. else{
  122. match k.key() {
  123. Key::ArrowUp =>{
  124. increase();
  125. }
  126. Key::ArrowDown =>{
  127. decrease();
  128. }
  129. _ => ()
  130. }
  131. }
  132. },
  133. onmousemove: move |evt| {
  134. if *dragging.get() {
  135. let offset = evt.data.element_coordinates();
  136. let mut new = Pos::new(offset.x as usize, offset.y as usize);
  137. if border != "none" {
  138. new.col = new.col.saturating_sub(1);
  139. }
  140. // textboxs are only one line tall
  141. new.row = 0;
  142. if new != cursor.read().start {
  143. cursor.write().end = Some(new);
  144. }
  145. }
  146. },
  147. onmousedown: move |evt| {
  148. let offset = evt.data.element_coordinates();
  149. let mut new = Pos::new(offset.x as usize, offset.y as usize);
  150. if border != "none" {
  151. new.col = new.col.saturating_sub(1);
  152. }
  153. new.row = 0;
  154. new.realize_col(&text_ref.read());
  155. cursor.set(Cursor::from_start(new));
  156. dragging.set(true);
  157. let node = tui_query_clone.get(get_root_id(cx).unwrap());
  158. let Point{ x, y } = node.pos().unwrap();
  159. let Pos { col, row } = cursor.read().start;
  160. let (x, y) = (col as u16 + x as u16 + if border == "none" {0} else {1}, row as u16 + y as u16 + if border == "none" {0} else {1});
  161. if let Ok(pos) = crossterm::cursor::position() {
  162. if pos != (x, y){
  163. execute!(stdout(), MoveTo(x, y)).unwrap();
  164. }
  165. }
  166. else{
  167. execute!(stdout(), MoveTo(x, y)).unwrap();
  168. }
  169. },
  170. onmouseup: move |_| {
  171. dragging.set(false);
  172. },
  173. onmouseleave: move |_| {
  174. dragging.set(false);
  175. },
  176. onmouseenter: move |_| {
  177. dragging.set(false);
  178. },
  179. onfocusout: |_| {
  180. execute!(stdout(), MoveTo(0, 1000)).unwrap();
  181. },
  182. "{text_before_first_cursor}"
  183. span{
  184. background_color: "rgba(255, 255, 255, 50%)",
  185. "{text_highlighted}"
  186. }
  187. "{text_after_second_cursor}"
  188. }
  189. }
  190. }