text_like.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. use std::{collections::HashMap, io::stdout};
  2. use crossterm::{cursor::MoveTo, execute};
  3. use dioxus_html::{input_data::keyboard_types::Key, KeyboardData, MouseData};
  4. use dioxus_native_core::{
  5. custom_element::CustomElement,
  6. node::OwnedAttributeDiscription,
  7. node_ref::AttributeMask,
  8. prelude::{ElementNode, NodeType},
  9. real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom},
  10. utils::cursor::{Cursor, Pos},
  11. NodeId,
  12. };
  13. use shipyard::UniqueView;
  14. use taffy::geometry::Point;
  15. use crate::{query::get_layout, Event, EventData, FormData, Query};
  16. use super::{RinkWidget, WidgetContext};
  17. pub(crate) trait TextLikeController {
  18. fn display_text(&self, text: &str) -> String {
  19. text.to_string()
  20. }
  21. }
  22. #[derive(Debug, Default)]
  23. pub(crate) struct EmptyController;
  24. impl TextLikeController for EmptyController {}
  25. #[derive(Debug, Default)]
  26. pub(crate) struct TextLike<C: TextLikeController = EmptyController> {
  27. text: String,
  28. div_wrapper: NodeId,
  29. pre_cursor_text: NodeId,
  30. highlighted_text: NodeId,
  31. post_cursor_text: NodeId,
  32. cursor: Cursor,
  33. dragging: bool,
  34. border: bool,
  35. max_len: Option<usize>,
  36. controller: C,
  37. }
  38. impl<C: TextLikeController> TextLike<C> {
  39. fn width(el: &ElementNodeMut) -> String {
  40. if let Some(value) = el
  41. .get_attribute(&OwnedAttributeDiscription {
  42. name: "width".to_string(),
  43. namespace: Some("style".to_string()),
  44. })
  45. .and_then(|value| value.as_text())
  46. .map(|value| value.to_string())
  47. {
  48. value
  49. } else {
  50. "1px".to_string()
  51. }
  52. }
  53. fn height(el: &ElementNodeMut) -> String {
  54. if let Some(value) = el
  55. .get_attribute(&OwnedAttributeDiscription {
  56. name: "height".to_string(),
  57. namespace: Some("style".to_string()),
  58. })
  59. .and_then(|value| value.as_text())
  60. .map(|value| value.to_string())
  61. {
  62. value
  63. } else {
  64. "1px".to_string()
  65. }
  66. }
  67. fn update_max_width_attr(&mut self, el: &ElementNodeMut) {
  68. if let Some(value) = el
  69. .get_attribute(&OwnedAttributeDiscription {
  70. name: "maxlength".to_string(),
  71. namespace: None,
  72. })
  73. .and_then(|value| value.as_text())
  74. .map(|value| value.to_string())
  75. {
  76. if let Ok(max_len) = value.parse::<usize>() {
  77. self.max_len = Some(max_len);
  78. }
  79. }
  80. }
  81. fn update_size_attr(&mut self, el: &mut ElementNodeMut) {
  82. let width = Self::width(el);
  83. let height = Self::height(el);
  84. let single_char = width
  85. .strip_prefix("px")
  86. .and_then(|n| n.parse::<u32>().ok().filter(|num| *num > 3))
  87. .is_some()
  88. || height
  89. .strip_prefix("px")
  90. .and_then(|n| n.parse::<u32>().ok().filter(|num| *num > 3))
  91. .is_some();
  92. self.border = !single_char;
  93. let border_style = if self.border { "solid" } else { "none" };
  94. el.set_attribute(
  95. OwnedAttributeDiscription {
  96. name: "border-style".to_string(),
  97. namespace: Some("style".to_string()),
  98. },
  99. border_style.to_string(),
  100. );
  101. }
  102. fn update_value_attr(&mut self, el: &ElementNodeMut) {
  103. if let Some(value) = el
  104. .get_attribute(&OwnedAttributeDiscription {
  105. name: "value".to_string(),
  106. namespace: None,
  107. })
  108. .and_then(|value| value.as_text())
  109. .map(|value| value.to_string())
  110. {
  111. self.text = value;
  112. }
  113. }
  114. pub(crate) fn set_text(&mut self, text: String, rdom: &mut RealDom, id: NodeId) {
  115. self.text = text;
  116. self.write_value(rdom, id);
  117. }
  118. pub(crate) fn text(&self) -> &str {
  119. self.text.as_str()
  120. }
  121. fn write_value(&self, rdom: &mut RealDom, id: NodeId) {
  122. let start_highlight = self.cursor.first().idx(self.text.as_str());
  123. let end_highlight = self.cursor.last().idx(self.text.as_str());
  124. let (text_before_first_cursor, text_after_first_cursor) =
  125. self.text.split_at(start_highlight);
  126. let (text_highlighted, text_after_second_cursor) =
  127. text_after_first_cursor.split_at(end_highlight - start_highlight);
  128. if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) {
  129. let node_type = text.node_type_mut();
  130. let NodeTypeMut::Text(mut text) = node_type else {
  131. panic!("input must be an element")
  132. };
  133. *text.text_mut() = self.controller.display_text(text_before_first_cursor);
  134. }
  135. if let Some(mut text) = rdom.get_mut(self.highlighted_text) {
  136. let node_type = text.node_type_mut();
  137. let NodeTypeMut::Text(mut text) = node_type else {
  138. panic!("input must be an element")
  139. };
  140. *text.text_mut() = self.controller.display_text(text_highlighted);
  141. }
  142. if let Some(mut text) = rdom.get_mut(self.post_cursor_text) {
  143. let node_type = text.node_type_mut();
  144. let NodeTypeMut::Text(mut text) = node_type else {
  145. panic!("input must be an element")
  146. };
  147. *text.text_mut() = self.controller.display_text(text_after_second_cursor);
  148. }
  149. // send the event
  150. {
  151. let world = rdom.raw_world_mut();
  152. let data: FormData = FormData {
  153. value: self.text.clone(),
  154. values: HashMap::new(),
  155. files: None,
  156. };
  157. let ctx: UniqueView<WidgetContext> = world.borrow().expect("expected widget context");
  158. ctx.send(Event {
  159. id,
  160. name: "input",
  161. data: EventData::Form(data),
  162. bubbles: true,
  163. });
  164. }
  165. }
  166. fn handle_keydown(&mut self, mut root: NodeMut, data: &KeyboardData) {
  167. let key = data.key();
  168. let modifiers = data.modifiers();
  169. let code = data.code();
  170. if key == Key::Enter {
  171. return;
  172. }
  173. self.cursor.handle_input(
  174. &code,
  175. &key,
  176. &modifiers,
  177. &mut self.text,
  178. self.max_len.unwrap_or(1000),
  179. );
  180. let id = root.id();
  181. let rdom = root.real_dom_mut();
  182. self.write_value(rdom, id);
  183. let world = rdom.raw_world_mut();
  184. // move cursor to new position
  185. let taffy = {
  186. let query: UniqueView<Query> = world.borrow().unwrap();
  187. query.stretch.clone()
  188. };
  189. let taffy = taffy.lock().unwrap();
  190. let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap();
  191. let Point { x, y } = layout.location;
  192. let Pos { col, row } = self.cursor.start;
  193. let (x, y) = (col as u16 + x as u16, row as u16 + y as u16);
  194. if let Ok(pos) = crossterm::cursor::position() {
  195. if pos != (x, y) {
  196. execute!(stdout(), MoveTo(x, y)).unwrap();
  197. }
  198. } else {
  199. execute!(stdout(), MoveTo(x, y)).unwrap();
  200. }
  201. }
  202. fn handle_mousemove(&mut self, mut root: NodeMut, data: &MouseData) {
  203. if self.dragging {
  204. let id = root.id();
  205. let offset = data.element_coordinates();
  206. let mut new = Pos::new(offset.x as usize, offset.y as usize);
  207. // textboxs are only one line tall
  208. new.row = 0;
  209. if new != self.cursor.start {
  210. self.cursor.end = Some(new);
  211. }
  212. let rdom = root.real_dom_mut();
  213. self.write_value(rdom, id);
  214. }
  215. }
  216. fn handle_mousedown(&mut self, mut root: NodeMut, data: &MouseData) {
  217. let offset = data.element_coordinates();
  218. let mut new = Pos::new(offset.x as usize, offset.y as usize);
  219. // textboxs are only one line tall
  220. new.row = 0;
  221. new.realize_col(self.text.as_str());
  222. self.cursor = Cursor::from_start(new);
  223. self.dragging = true;
  224. let id = root.id();
  225. // move cursor to new position
  226. let rdom = root.real_dom_mut();
  227. let world = rdom.raw_world_mut();
  228. let taffy = {
  229. let query: UniqueView<Query> = world.borrow().unwrap();
  230. query.stretch.clone()
  231. };
  232. let taffy = taffy.lock().unwrap();
  233. let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap();
  234. let Point { x, y } = layout.location;
  235. let Pos { col, row } = self.cursor.start;
  236. let (x, y) = (col as u16 + x as u16, row as u16 + y as u16);
  237. if let Ok(pos) = crossterm::cursor::position() {
  238. if pos != (x, y) {
  239. execute!(stdout(), MoveTo(x, y)).unwrap();
  240. }
  241. } else {
  242. execute!(stdout(), MoveTo(x, y)).unwrap();
  243. }
  244. self.write_value(rdom, id)
  245. }
  246. }
  247. impl<C: TextLikeController + Send + Sync + Default + 'static> CustomElement for TextLike<C> {
  248. const NAME: &'static str = "input";
  249. fn roots(&self) -> Vec<NodeId> {
  250. vec![self.div_wrapper]
  251. }
  252. fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self {
  253. let node_type = root.node_type();
  254. let NodeType::Element(el) = &*node_type else {
  255. panic!("input must be an element")
  256. };
  257. let value = el
  258. .attributes
  259. .get(&OwnedAttributeDiscription {
  260. name: "value".to_string(),
  261. namespace: None,
  262. })
  263. .and_then(|value| value.as_text())
  264. .map(|value| value.to_string());
  265. drop(node_type);
  266. let rdom = root.real_dom_mut();
  267. let pre_text = rdom.create_node(String::new());
  268. let pre_text_id = pre_text.id();
  269. let highlighted_text = rdom.create_node(String::new());
  270. let highlighted_text_id = highlighted_text.id();
  271. let mut highlighted_text_span = rdom.create_node(NodeType::Element(ElementNode {
  272. tag: "span".to_string(),
  273. attributes: [(
  274. OwnedAttributeDiscription {
  275. name: "background-color".to_string(),
  276. namespace: Some("style".to_string()),
  277. },
  278. "rgba(255, 255, 255, 50%)".to_string().into(),
  279. )]
  280. .into_iter()
  281. .collect(),
  282. ..Default::default()
  283. }));
  284. highlighted_text_span.add_child(highlighted_text_id);
  285. let highlighted_text_span_id = highlighted_text_span.id();
  286. let post_text = rdom.create_node(value.clone().unwrap_or_default());
  287. let post_text_id = post_text.id();
  288. let mut div_wrapper = rdom.create_node(NodeType::Element(ElementNode {
  289. tag: "div".to_string(),
  290. attributes: [(
  291. OwnedAttributeDiscription {
  292. name: "display".to_string(),
  293. namespace: Some("style".to_string()),
  294. },
  295. "flex".to_string().into(),
  296. )]
  297. .into_iter()
  298. .collect(),
  299. ..Default::default()
  300. }));
  301. let div_wrapper_id = div_wrapper.id();
  302. div_wrapper.add_child(pre_text_id);
  303. div_wrapper.add_child(highlighted_text_span_id);
  304. div_wrapper.add_child(post_text_id);
  305. div_wrapper.add_event_listener("mousemove");
  306. div_wrapper.add_event_listener("mousedown");
  307. div_wrapper.add_event_listener("mouseup");
  308. div_wrapper.add_event_listener("mouseleave");
  309. div_wrapper.add_event_listener("mouseenter");
  310. root.add_event_listener("keydown");
  311. root.add_event_listener("focusout");
  312. Self {
  313. pre_cursor_text: pre_text_id,
  314. highlighted_text: highlighted_text_id,
  315. post_cursor_text: post_text_id,
  316. div_wrapper: div_wrapper_id,
  317. cursor: Cursor::default(),
  318. text: value.unwrap_or_default(),
  319. ..Default::default()
  320. }
  321. }
  322. fn attributes_changed(
  323. &mut self,
  324. mut root: dioxus_native_core::real_dom::NodeMut,
  325. attributes: &dioxus_native_core::node_ref::AttributeMask,
  326. ) {
  327. match attributes {
  328. AttributeMask::All => {
  329. {
  330. let node_type = root.node_type_mut();
  331. let NodeTypeMut::Element(mut el) = node_type else {
  332. panic!("input must be an element")
  333. };
  334. self.update_value_attr(&el);
  335. self.update_size_attr(&mut el);
  336. self.update_max_width_attr(&el);
  337. }
  338. let id = root.id();
  339. self.write_value(root.real_dom_mut(), id);
  340. }
  341. AttributeMask::Some(attrs) => {
  342. {
  343. let node_type = root.node_type_mut();
  344. let NodeTypeMut::Element(mut el) = node_type else {
  345. panic!("input must be an element")
  346. };
  347. if attrs.contains("width") || attrs.contains("height") {
  348. self.update_size_attr(&mut el);
  349. }
  350. if attrs.contains("maxlength") {
  351. self.update_max_width_attr(&el);
  352. }
  353. if attrs.contains("value") {
  354. self.update_value_attr(&el);
  355. }
  356. }
  357. if attrs.contains("value") {
  358. let id = root.id();
  359. self.write_value(root.real_dom_mut(), id);
  360. }
  361. }
  362. }
  363. }
  364. }
  365. impl<C: TextLikeController + Send + Sync + Default + 'static> RinkWidget for TextLike<C> {
  366. fn handle_event(&mut self, event: &crate::Event, node: NodeMut) {
  367. match event.name {
  368. "keydown" => {
  369. if let EventData::Keyboard(data) = &event.data {
  370. self.handle_keydown(node, data);
  371. }
  372. }
  373. "mousemove" => {
  374. if let EventData::Mouse(data) = &event.data {
  375. self.handle_mousemove(node, data);
  376. }
  377. }
  378. "mousedown" => {
  379. if let EventData::Mouse(data) = &event.data {
  380. self.handle_mousedown(node, data);
  381. }
  382. }
  383. "mouseup" => {
  384. self.dragging = false;
  385. }
  386. "mouseleave" => {
  387. self.dragging = false;
  388. }
  389. "mouseenter" => {
  390. self.dragging = false;
  391. }
  392. "focusout" => {
  393. execute!(stdout(), MoveTo(0, 1000)).unwrap();
  394. }
  395. _ => {}
  396. }
  397. }
  398. }