number.rs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. files: None,
  81. });
  82. }
  83. };
  84. let increase = move || {
  85. let mut text = text_ref.write();
  86. *text = (text.parse::<f64>().unwrap_or(0.0) + 1.0).to_string();
  87. update(text.clone());
  88. };
  89. let decrease = move || {
  90. let mut text = text_ref.write();
  91. *text = (text.parse::<f64>().unwrap_or(0.0) - 1.0).to_string();
  92. update(text.clone());
  93. };
  94. cx.render(rsx! {
  95. div{
  96. width: "{width}",
  97. height: "{height}",
  98. border_style: "{border}",
  99. onkeydown: move |k| {
  100. let is_text = match k.key(){
  101. Key::ArrowLeft | Key::ArrowRight | Key::Backspace => true,
  102. Key::Character(c) if c=="." || c== "-" || c.chars().all(|c|c.is_numeric())=> true,
  103. _ => false,
  104. };
  105. if is_text{
  106. let mut text = text_ref.write();
  107. cursor.write().handle_input(&k, &mut *text, max_len);
  108. update(text.clone());
  109. let node = tui_query.get(get_root_id(cx).unwrap());
  110. let Point{ x, y } = node.pos().unwrap();
  111. let Pos { col, row } = cursor.read().start;
  112. let (x, y) = (col as u16 + x as u16 + u16::from(border != "none"), row as u16 + y as u16 + u16::from(border != "none"));
  113. if let Ok(pos) = crossterm::cursor::position() {
  114. if pos != (x, y){
  115. execute!(stdout(), MoveTo(x, y)).unwrap();
  116. }
  117. }
  118. else{
  119. execute!(stdout(), MoveTo(x, y)).unwrap();
  120. }
  121. }
  122. else{
  123. match k.key() {
  124. Key::ArrowUp =>{
  125. increase();
  126. }
  127. Key::ArrowDown =>{
  128. decrease();
  129. }
  130. _ => ()
  131. }
  132. }
  133. },
  134. onmousemove: move |evt| {
  135. if *dragging.get() {
  136. let offset = evt.data.element_coordinates();
  137. let mut new = Pos::new(offset.x as usize, offset.y as usize);
  138. if border != "none" {
  139. new.col = new.col.saturating_sub(1);
  140. }
  141. // textboxs are only one line tall
  142. new.row = 0;
  143. if new != cursor.read().start {
  144. cursor.write().end = Some(new);
  145. }
  146. }
  147. },
  148. onmousedown: move |evt| {
  149. let offset = evt.data.element_coordinates();
  150. let mut new = Pos::new(offset.x as usize, offset.y as usize);
  151. if border != "none" {
  152. new.col = new.col.saturating_sub(1);
  153. }
  154. new.row = 0;
  155. new.realize_col(text_ref.read().as_str());
  156. cursor.set(Cursor::from_start(new));
  157. dragging.set(true);
  158. let node = tui_query_clone.get(get_root_id(cx).unwrap());
  159. let Point{ x, y } = node.pos().unwrap();
  160. let Pos { col, row } = cursor.read().start;
  161. let (x, y) = (col as u16 + x as u16 + u16::from(border != "none"), row as u16 + y as u16 + u16::from(border != "none"));
  162. if let Ok(pos) = crossterm::cursor::position() {
  163. if pos != (x, y){
  164. execute!(stdout(), MoveTo(x, y)).unwrap();
  165. }
  166. }
  167. else{
  168. execute!(stdout(), MoveTo(x, y)).unwrap();
  169. }
  170. },
  171. onmouseup: move |_| {
  172. dragging.set(false);
  173. },
  174. onmouseleave: move |_| {
  175. dragging.set(false);
  176. },
  177. onmouseenter: move |_| {
  178. dragging.set(false);
  179. },
  180. onfocusout: |_| {
  181. execute!(stdout(), MoveTo(0, 1000)).unwrap();
  182. },
  183. "{text_before_first_cursor}"
  184. span{
  185. background_color: "rgba(255, 255, 255, 50%)",
  186. "{text_highlighted}"
  187. }
  188. "{text_after_second_cursor}"
  189. }
  190. })
  191. }