password.rs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. use crate::widgets::get_root_id;
  2. use crate::Query;
  3. use crossterm::{cursor::*, 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 PasswordProps<'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 Password<'a>(cx: Scope<'a, PasswordProps>) -> 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 text_before_first_cursor = ".".repeat(text_before_first_cursor.len());
  46. let text_highlighted = ".".repeat(text_highlighted.len());
  47. let text_after_second_cursor = ".".repeat(text_after_second_cursor.len());
  48. let max_len = cx
  49. .props
  50. .max_length
  51. .as_ref()
  52. .and_then(|s| s.parse().ok())
  53. .unwrap_or(usize::MAX);
  54. let width = cx
  55. .props
  56. .width
  57. .map(|s| s.to_string())
  58. // px is the same as em in tui
  59. .or_else(|| cx.props.size.map(|s| s.to_string() + "px"))
  60. .unwrap_or_else(|| "10px".to_string());
  61. let height = cx.props.height.unwrap_or("3px");
  62. // don't draw a border unless there is enough space
  63. let border = if width
  64. .strip_suffix("px")
  65. .and_then(|w| w.parse::<i32>().ok())
  66. .filter(|w| *w < 3)
  67. .is_some()
  68. || height
  69. .strip_suffix("px")
  70. .and_then(|h| h.parse::<i32>().ok())
  71. .filter(|h| *h < 3)
  72. .is_some()
  73. {
  74. "none"
  75. } else {
  76. "solid"
  77. };
  78. render! {
  79. div{
  80. width: "{width}",
  81. height: "{height}",
  82. border_style: "{border}",
  83. onkeydown: move |k| {
  84. if k.key()== Key::Enter {
  85. return;
  86. }
  87. let mut text = text_ref.write();
  88. cursor.write().handle_input(&k, &mut text, max_len);
  89. if let Some(input_handler) = &cx.props.raw_oninput{
  90. input_handler.call(FormData{
  91. value: text.clone(),
  92. values: HashMap::new(),
  93. files: None
  94. });
  95. }
  96. let node = tui_query.get(get_root_id(cx).unwrap());
  97. let Point{ x, y } = node.pos().unwrap();
  98. let Pos { col, row } = cursor.read().start;
  99. 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});
  100. if let Ok(pos) = crossterm::cursor::position() {
  101. if pos != (x, y){
  102. execute!(stdout(), MoveTo(x, y)).unwrap();
  103. }
  104. }
  105. else{
  106. execute!(stdout(), MoveTo(x, y)).unwrap();
  107. }
  108. },
  109. onmousemove: move |evt| {
  110. if *dragging.get() {
  111. let offset = evt.data.element_coordinates();
  112. let mut new = Pos::new(offset.x as usize, offset.y as usize);
  113. if border != "none" {
  114. new.col = new.col.saturating_sub(1);
  115. }
  116. // textboxs are only one line tall
  117. new.row = 0;
  118. if new != cursor.read().start {
  119. cursor.write().end = Some(new);
  120. }
  121. }
  122. },
  123. onmousedown: move |evt| {
  124. let offset = evt.data.element_coordinates();
  125. let mut new = Pos::new(offset.x as usize, offset.y as usize);
  126. if border != "none" {
  127. new.col = new.col.saturating_sub(1);
  128. }
  129. // textboxs are only one line tall
  130. new.row = 0;
  131. new.realize_col(&text_ref.read());
  132. cursor.set(Cursor::from_start(new));
  133. dragging.set(true);
  134. let node = tui_query_clone.get(get_root_id(cx).unwrap());
  135. let Point{ x, y } = node.pos().unwrap();
  136. let Pos { col, row } = cursor.read().start;
  137. 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});
  138. if let Ok(pos) = crossterm::cursor::position() {
  139. if pos != (x, y){
  140. execute!(stdout(), MoveTo(x, y)).unwrap();
  141. }
  142. }
  143. else{
  144. execute!(stdout(), MoveTo(x, y)).unwrap();
  145. }
  146. },
  147. onmouseup: move |_| {
  148. dragging.set(false);
  149. },
  150. onmouseleave: move |_| {
  151. dragging.set(false);
  152. },
  153. onmouseenter: move |_| {
  154. dragging.set(false);
  155. },
  156. onfocusout: |_| {
  157. execute!(stdout(), MoveTo(0, 1000)).unwrap();
  158. },
  159. "{text_before_first_cursor}"
  160. span{
  161. background_color: "rgba(255, 255, 255, 50%)",
  162. "{text_highlighted}"
  163. }
  164. "{text_after_second_cursor}"
  165. }
  166. }
  167. }