From baeb4251bab9b601be4f994534627f98ecf6df1f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 16 Apr 2023 14:05:27 -0500 Subject: [PATCH] refactor text inputs --- packages/rink/src/widgets/mod.rs | 1 + packages/rink/src/widgets/number.rs | 465 +++---------------------- packages/rink/src/widgets/password.rs | 422 +--------------------- packages/rink/src/widgets/text_like.rs | 444 +++++++++++++++++++++++ packages/rink/src/widgets/textbox.rs | 424 +--------------------- 5 files changed, 504 insertions(+), 1252 deletions(-) create mode 100644 packages/rink/src/widgets/text_like.rs diff --git a/packages/rink/src/widgets/mod.rs b/packages/rink/src/widgets/mod.rs index 27dea751d..c3ecaad78 100644 --- a/packages/rink/src/widgets/mod.rs +++ b/packages/rink/src/widgets/mod.rs @@ -4,6 +4,7 @@ mod input; mod number; mod password; mod slider; +mod text_like; mod textbox; use std::sync::{Arc, RwLock}; diff --git a/packages/rink/src/widgets/number.rs b/packages/rink/src/widgets/number.rs index c83d514b5..371c8b41d 100644 --- a/packages/rink/src/widgets/number.rs +++ b/packages/rink/src/widgets/number.rs @@ -1,291 +1,28 @@ -use std::{collections::HashMap, io::stdout}; - -use crossterm::{cursor::MoveTo, execute}; -use dioxus_html::{input_data::keyboard_types::Key, KeyboardData, MouseData}; +use dioxus_html::input_data::keyboard_types::Key; use dioxus_native_core::{ custom_element::CustomElement, - node::OwnedAttributeDiscription, - node_ref::AttributeMask, - prelude::{ElementNode, NodeType}, - real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom}, - utils::cursor::{Cursor, Pos}, + real_dom::{NodeImmutable, RealDom}, NodeId, }; -use shipyard::UniqueView; -use taffy::geometry::Point; -use crate::{query::get_layout, Event, EventData, FormData, Query}; +use crate::EventData; -use super::{RinkWidget, WidgetContext}; +use super::{text_like::TextLike, RinkWidget}; #[derive(Debug, Default)] pub(crate) struct Number { - text: String, - div_wrapper: NodeId, - pre_cursor_text: NodeId, - highlighted_text: NodeId, - post_cursor_text: NodeId, - cursor: Cursor, - dragging: bool, - border: bool, - max_len: Option, + text: TextLike, } impl Number { - fn width(el: &ElementNodeMut) -> String { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "width".to_string(), - namespace: Some("style".to_string()), - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - value - } else { - "1px".to_string() - } + fn increase(&mut self, rdom: &mut RealDom, id: NodeId) { + let num = self.text.text().parse::().unwrap_or(0.0); + self.text.set_text((num + 1.0).to_string(), rdom, id); } - fn height(el: &ElementNodeMut) -> String { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "height".to_string(), - namespace: Some("style".to_string()), - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - value - } else { - "1px".to_string() - } - } - - fn update_max_width_attr(&mut self, el: &ElementNodeMut) { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "maxlength".to_string(), - namespace: None, - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - if let Ok(max_len) = value.parse::() { - self.max_len = Some(max_len); - } - } - } - - fn update_size_attr(&mut self, el: &mut ElementNodeMut) { - let width = Self::width(el); - let height = Self::height(el); - let single_char = width - .strip_prefix("px") - .and_then(|n| n.parse::().ok().filter(|num| *num > 3)) - .is_some() - || height - .strip_prefix("px") - .and_then(|n| n.parse::().ok().filter(|num| *num > 3)) - .is_some(); - self.border = !single_char; - let border_style = if self.border { "solid" } else { "none" }; - el.set_attribute( - OwnedAttributeDiscription { - name: "border-style".to_string(), - namespace: Some("style".to_string()), - }, - border_style.to_string(), - ); - } - - fn update_value_attr(&mut self, el: &ElementNodeMut) { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "value".to_string(), - namespace: None, - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - self.text = value; - } - } - - fn write_value(&self, rdom: &mut RealDom, id: NodeId) { - let start_highlight = self.cursor.first().idx(self.text.as_str()); - let end_highlight = self.cursor.last().idx(self.text.as_str()); - let (text_before_first_cursor, text_after_first_cursor) = - self.text.split_at(start_highlight); - let (text_highlighted, text_after_second_cursor) = - text_after_first_cursor.split_at(end_highlight - start_highlight); - - if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) { - let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; - *text.text_mut() = text_before_first_cursor.to_string(); - } - - if let Some(mut text) = rdom.get_mut(self.highlighted_text) { - let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; - *text.text_mut() = text_highlighted.to_string(); - } - - if let Some(mut text) = rdom.get_mut(self.post_cursor_text) { - let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; - *text.text_mut() = text_after_second_cursor.to_string(); - } - - // send the event - { - let world = rdom.raw_world_mut(); - let data = FormData { - value: self.text.clone(), - values: HashMap::new(), - files: None, - }; - let ctx: UniqueView = world.borrow().expect("expected widget context"); - - ctx.send(Event { - id, - name: "input", - data: EventData::Form(data), - bubbles: true, - }); - } - } - - fn increase(&mut self) { - let num = self.text.parse::().unwrap_or(0.0); - self.text = (num + 1.0).to_string(); - } - - fn decrease(&mut self) { - let num = self.text.parse::().unwrap_or(0.0); - self.text = (num - 1.0).to_string(); - } - - fn handle_keydown(&mut self, mut root: NodeMut, data: &KeyboardData) { - let key = data.key(); - let is_text = match key.clone() { - Key::ArrowLeft | Key::ArrowRight | Key::Backspace => true, - Key::Character(c) if c == "." || c == "-" || c.chars().all(|c| c.is_numeric()) => true, - _ => false, - }; - - if is_text { - let modifiers = data.modifiers(); - let code = data.code(); - - if key == Key::Enter { - return; - } - self.cursor.handle_input( - &code, - &key, - &modifiers, - &mut self.text, - self.max_len.unwrap_or(1000), - ); - - let id = root.id(); - - let rdom = root.real_dom_mut(); - self.write_value(rdom, id); - let world = rdom.raw_world_mut(); - - // move cursor to new position - let taffy = { - let query: UniqueView = world.borrow().unwrap(); - query.stretch.clone() - }; - - let taffy = taffy.lock().unwrap(); - - let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap(); - let Point { x, y } = layout.location; - - let Pos { col, row } = self.cursor.start; - let (x, y) = (col as u16 + x as u16, row as u16 + y as u16); - if let Ok(pos) = crossterm::cursor::position() { - if pos != (x, y) { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - } else { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - } else { - match key { - Key::ArrowUp => { - self.increase(); - } - Key::ArrowDown => { - self.decrease(); - } - _ => (), - } - - let id = root.id(); - let rdom = root.real_dom_mut(); - self.write_value(rdom, id); - } - } - - fn handle_mousemove(&mut self, mut root: NodeMut, data: &MouseData) { - if self.dragging { - let id = root.id(); - let offset = data.element_coordinates(); - let mut new = Pos::new(offset.x as usize, offset.y as usize); - // textboxs are only one line tall - new.row = 0; - - if new != self.cursor.start { - self.cursor.end = Some(new); - } - let rdom = root.real_dom_mut(); - self.write_value(rdom, id); - } - } - - fn handle_mousedown(&mut self, mut root: NodeMut, data: &MouseData) { - let offset = data.element_coordinates(); - let mut new = Pos::new(offset.x as usize, offset.y as usize); - - // textboxs are only one line tall - new.row = 0; - - new.realize_col(self.text.as_str()); - self.cursor = Cursor::from_start(new); - self.dragging = true; - - let id = root.id(); - - // move cursor to new position - let rdom = root.real_dom_mut(); - let world = rdom.raw_world_mut(); - let taffy = { - let query: UniqueView = world.borrow().unwrap(); - query.stretch.clone() - }; - - let taffy = taffy.lock().unwrap(); - - let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap(); - let Point { x, y } = layout.location; - - let Pos { col, row } = self.cursor.start; - let (x, y) = (col as u16 + x as u16, row as u16 + y as u16); - if let Ok(pos) = crossterm::cursor::position() { - if pos != (x, y) { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - } else { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - - self.write_value(rdom, id) + fn decrease(&mut self, rdom: &mut RealDom, id: NodeId) { + let num = self.text.text().parse::().unwrap_or(0.0); + self.text.set_text((num - 1.0).to_string(), rdom, id); } } @@ -293,162 +30,62 @@ impl CustomElement for Number { const NAME: &'static str = "input"; fn roots(&self) -> Vec { - vec![self.div_wrapper] + self.text.roots() } fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { - let node_type = root.node_type(); - let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; - - let value = el - .attributes - .get(&OwnedAttributeDiscription { - name: "value".to_string(), - namespace: None, - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()); - - drop(node_type); - - let rdom = root.real_dom_mut(); - - let pre_text = rdom.create_node(String::new()); - let pre_text_id = pre_text.id(); - let highlighted_text = rdom.create_node(String::new()); - let highlighted_text_id = highlighted_text.id(); - let mut highlighted_text_span = rdom.create_node(NodeType::Element(ElementNode { - tag: "span".to_string(), - attributes: [( - OwnedAttributeDiscription { - name: "background-color".to_string(), - namespace: Some("style".to_string()), - }, - "rgba(255, 255, 255, 50%)".to_string().into(), - )] - .into_iter() - .collect(), - ..Default::default() - })); - highlighted_text_span.add_child(highlighted_text_id); - let highlighted_text_span_id = highlighted_text_span.id(); - let post_text = rdom.create_node(value.clone().unwrap_or_default()); - let post_text_id = post_text.id(); - let mut div_wrapper = rdom.create_node(NodeType::Element(ElementNode { - tag: "div".to_string(), - attributes: [( - OwnedAttributeDiscription { - name: "display".to_string(), - namespace: Some("style".to_string()), - }, - "flex".to_string().into(), - )] - .into_iter() - .collect(), - ..Default::default() - })); - let div_wrapper_id = div_wrapper.id(); - div_wrapper.add_child(pre_text_id); - div_wrapper.add_child(highlighted_text_span_id); - div_wrapper.add_child(post_text_id); - - div_wrapper.add_event_listener("mousemove"); - div_wrapper.add_event_listener("mousedown"); - div_wrapper.add_event_listener("mouseup"); - div_wrapper.add_event_listener("mouseleave"); - div_wrapper.add_event_listener("mouseenter"); - root.add_event_listener("keydown"); - root.add_event_listener("focusout"); - - Self { - pre_cursor_text: pre_text_id, - highlighted_text: highlighted_text_id, - post_cursor_text: post_text_id, - div_wrapper: div_wrapper_id, - cursor: Cursor::default(), - text: value.unwrap_or_default(), - ..Default::default() + Number { + text: TextLike::create(root.reborrow()), } } fn attributes_changed( &mut self, - mut root: dioxus_native_core::real_dom::NodeMut, + root: dioxus_native_core::real_dom::NodeMut, attributes: &dioxus_native_core::node_ref::AttributeMask, ) { - match attributes { - AttributeMask::All => { - { - let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; - self.update_value_attr(&el); - self.update_size_attr(&mut el); - self.update_max_width_attr(&el); - } - let id = root.id(); - self.write_value(root.real_dom_mut(), id); - } - AttributeMask::Some(attrs) => { - { - let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; - if attrs.contains("width") || attrs.contains("height") { - self.update_size_attr(&mut el); - } - if attrs.contains("maxlength") { - self.update_max_width_attr(&el); - } - if attrs.contains("value") { - self.update_value_attr(&el); - } - } - if attrs.contains("value") { - let id = root.id(); - self.write_value(root.real_dom_mut(), id); - } - } - } + self.text.attributes_changed(root, attributes) } } impl RinkWidget for Number { - fn handle_event(&mut self, event: &crate::Event, node: dioxus_native_core::real_dom::NodeMut) { - match event.name { - "keydown" => { - if let EventData::Keyboard(data) = &event.data { - self.handle_keydown(node, data); + fn handle_event( + &mut self, + event: &crate::Event, + mut node: dioxus_native_core::real_dom::NodeMut, + ) { + if event.name == "keydown" { + if let EventData::Keyboard(data) = &event.data { + let key = data.key(); + let is_num_like = match key.clone() { + Key::ArrowLeft | Key::ArrowRight | Key::Backspace => true, + Key::Character(c) + if c == "." || c == "-" || c.chars().all(|c| c.is_numeric()) => + { + true + } + _ => false, + }; + + if is_num_like { + self.text.handle_event(event, node) + } else { + let id = node.id(); + let rdom = node.real_dom_mut(); + match key { + Key::ArrowUp => { + self.increase(rdom, id); + } + Key::ArrowDown => { + self.decrease(rdom, id); + } + _ => (), + } } + return; } - - "mousemove" => { - if let EventData::Mouse(data) = &event.data { - self.handle_mousemove(node, data); - } - } - - "mousedown" => { - if let EventData::Mouse(data) = &event.data { - self.handle_mousedown(node, data); - } - } - - "mouseup" => { - self.dragging = false; - } - - "mouseleave" => { - self.dragging = false; - } - - "mouseenter" => { - self.dragging = false; - } - - "focusout" => { - execute!(stdout(), MoveTo(0, 1000)).unwrap(); - } - - _ => {} } + + self.text.handle_event(event, node) } } diff --git a/packages/rink/src/widgets/password.rs b/packages/rink/src/widgets/password.rs index 7e570a631..9a8228ad1 100644 --- a/packages/rink/src/widgets/password.rs +++ b/packages/rink/src/widgets/password.rs @@ -1,422 +1,12 @@ -use std::{collections::HashMap, io::stdout}; +use super::text_like::{TextLike, TextLikeController}; -use crossterm::{cursor::MoveTo, execute}; -use dioxus_html::{input_data::keyboard_types::Key, KeyboardData, MouseData}; -use dioxus_native_core::{ - custom_element::CustomElement, - node::OwnedAttributeDiscription, - node_ref::AttributeMask, - prelude::{ElementNode, NodeType}, - real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom}, - utils::cursor::{Cursor, Pos}, - NodeId, -}; -use shipyard::UniqueView; -use taffy::geometry::Point; - -use crate::{query::get_layout, Event, EventData, FormData, Query}; - -use super::{RinkWidget, WidgetContext}; +pub(crate) type Password = TextLike; #[derive(Debug, Default)] -pub(crate) struct Password { - text: String, - div_wrapper: NodeId, - pre_cursor_text: NodeId, - highlighted_text: NodeId, - post_cursor_text: NodeId, - cursor: Cursor, - dragging: bool, - border: bool, - max_len: Option, -} +pub(crate) struct PasswordController; -impl Password { - fn width(el: &ElementNodeMut) -> String { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "width".to_string(), - namespace: Some("style".to_string()), - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - value - } else { - "1px".to_string() - } - } - - fn height(el: &ElementNodeMut) -> String { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "height".to_string(), - namespace: Some("style".to_string()), - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - value - } else { - "1px".to_string() - } - } - - fn update_max_width_attr(&mut self, el: &ElementNodeMut) { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "maxlength".to_string(), - namespace: None, - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - if let Ok(max_len) = value.parse::() { - self.max_len = Some(max_len); - } - } - } - - fn update_size_attr(&mut self, el: &mut ElementNodeMut) { - let width = Self::width(el); - let height = Self::height(el); - let single_char = width - .strip_prefix("px") - .and_then(|n| n.parse::().ok().filter(|num| *num > 3)) - .is_some() - || height - .strip_prefix("px") - .and_then(|n| n.parse::().ok().filter(|num| *num > 3)) - .is_some(); - self.border = !single_char; - let border_style = if self.border { "solid" } else { "none" }; - el.set_attribute( - OwnedAttributeDiscription { - name: "border-style".to_string(), - namespace: Some("style".to_string()), - }, - border_style.to_string(), - ); - } - - fn update_value_attr(&mut self, el: &ElementNodeMut) { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "value".to_string(), - namespace: None, - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - self.text = value; - } - } - - fn write_value(&self, rdom: &mut RealDom, id: NodeId) { - let start_highlight = self.cursor.first().idx(self.text.as_str()); - let end_highlight = self.cursor.last().idx(self.text.as_str()); - let (text_before_first_cursor, text_after_first_cursor) = - self.text.split_at(start_highlight); - let (text_highlighted, text_after_second_cursor) = - text_after_first_cursor.split_at(end_highlight - start_highlight); - - if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) { - let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; - *text.text_mut() = ".".repeat(text_before_first_cursor.len()); - } - - if let Some(mut text) = rdom.get_mut(self.highlighted_text) { - let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; - *text.text_mut() = ".".repeat(text_highlighted.len()); - } - - if let Some(mut text) = rdom.get_mut(self.post_cursor_text) { - let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; - *text.text_mut() = ".".repeat(text_after_second_cursor.len()); - } - - // send the event - { - let world = rdom.raw_world_mut(); - let data = FormData { - value: self.text.clone(), - values: HashMap::new(), - files: None, - }; - let ctx: UniqueView = world.borrow().expect("expected widget context"); - - ctx.send(Event { - id, - name: "input", - data: EventData::Form(data), - bubbles: true, - }); - } - } - - fn handle_keydown(&mut self, mut root: NodeMut, data: &KeyboardData) { - let key = data.key(); - let modifiers = data.modifiers(); - let code = data.code(); - - if key == Key::Enter { - return; - } - self.cursor.handle_input( - &code, - &key, - &modifiers, - &mut self.text, - self.max_len.unwrap_or(1000), - ); - - let id = root.id(); - - let rdom = root.real_dom_mut(); - self.write_value(rdom, id); - let world = rdom.raw_world_mut(); - - // move cursor to new position - let taffy = { - let query: UniqueView = world.borrow().unwrap(); - query.stretch.clone() - }; - - let taffy = taffy.lock().unwrap(); - - let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap(); - let Point { x, y } = layout.location; - - let Pos { col, row } = self.cursor.start; - let (x, y) = (col as u16 + x as u16, row as u16 + y as u16); - if let Ok(pos) = crossterm::cursor::position() { - if pos != (x, y) { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - } else { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - } - - fn handle_mousemove(&mut self, mut root: NodeMut, data: &MouseData) { - if self.dragging { - let id = root.id(); - let offset = data.element_coordinates(); - let mut new = Pos::new(offset.x as usize, offset.y as usize); - // textboxs are only one line tall - new.row = 0; - - if new != self.cursor.start { - self.cursor.end = Some(new); - } - let rdom = root.real_dom_mut(); - self.write_value(rdom, id); - } - } - - fn handle_mousedown(&mut self, mut root: NodeMut, data: &MouseData) { - let offset = data.element_coordinates(); - let mut new = Pos::new(offset.x as usize, offset.y as usize); - - // textboxs are only one line tall - new.row = 0; - - new.realize_col(self.text.as_str()); - self.cursor = Cursor::from_start(new); - self.dragging = true; - - let id = root.id(); - - // move cursor to new position - let rdom = root.real_dom_mut(); - let world = rdom.raw_world_mut(); - let taffy = { - let query: UniqueView = world.borrow().unwrap(); - query.stretch.clone() - }; - - let taffy = taffy.lock().unwrap(); - - let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap(); - let Point { x, y } = layout.location; - - let Pos { col, row } = self.cursor.start; - let (x, y) = (col as u16 + x as u16, row as u16 + y as u16); - if let Ok(pos) = crossterm::cursor::position() { - if pos != (x, y) { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - } else { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - - self.write_value(rdom, id) - } -} - -impl CustomElement for Password { - const NAME: &'static str = "input"; - - fn roots(&self) -> Vec { - vec![self.div_wrapper] - } - - fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { - let node_type = root.node_type(); - let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; - - let value = el - .attributes - .get(&OwnedAttributeDiscription { - name: "value".to_string(), - namespace: None, - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()); - - drop(node_type); - - let rdom = root.real_dom_mut(); - - let pre_text = rdom.create_node(String::new()); - let pre_text_id = pre_text.id(); - let highlighted_text = rdom.create_node(String::new()); - let highlighted_text_id = highlighted_text.id(); - let mut highlighted_text_span = rdom.create_node(NodeType::Element(ElementNode { - tag: "span".to_string(), - attributes: [( - OwnedAttributeDiscription { - name: "background-color".to_string(), - namespace: Some("style".to_string()), - }, - "rgba(255, 255, 255, 50%)".to_string().into(), - )] - .into_iter() - .collect(), - ..Default::default() - })); - highlighted_text_span.add_child(highlighted_text_id); - let highlighted_text_span_id = highlighted_text_span.id(); - let post_text = rdom.create_node(value.clone().unwrap_or_default()); - let post_text_id = post_text.id(); - let mut div_wrapper = rdom.create_node(NodeType::Element(ElementNode { - tag: "div".to_string(), - attributes: [( - OwnedAttributeDiscription { - name: "display".to_string(), - namespace: Some("style".to_string()), - }, - "flex".to_string().into(), - )] - .into_iter() - .collect(), - ..Default::default() - })); - let div_wrapper_id = div_wrapper.id(); - div_wrapper.add_child(pre_text_id); - div_wrapper.add_child(highlighted_text_span_id); - div_wrapper.add_child(post_text_id); - - div_wrapper.add_event_listener("mousemove"); - div_wrapper.add_event_listener("mousedown"); - div_wrapper.add_event_listener("mouseup"); - div_wrapper.add_event_listener("mouseleave"); - div_wrapper.add_event_listener("mouseenter"); - root.add_event_listener("keydown"); - root.add_event_listener("focusout"); - - Self { - pre_cursor_text: pre_text_id, - highlighted_text: highlighted_text_id, - post_cursor_text: post_text_id, - div_wrapper: div_wrapper_id, - cursor: Cursor::default(), - text: value.unwrap_or_default(), - ..Default::default() - } - } - - fn attributes_changed( - &mut self, - mut root: dioxus_native_core::real_dom::NodeMut, - attributes: &dioxus_native_core::node_ref::AttributeMask, - ) { - match attributes { - AttributeMask::All => { - { - let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; - self.update_value_attr(&el); - self.update_size_attr(&mut el); - self.update_max_width_attr(&el); - } - let id = root.id(); - self.write_value(root.real_dom_mut(), id); - } - AttributeMask::Some(attrs) => { - { - let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; - if attrs.contains("width") || attrs.contains("height") { - self.update_size_attr(&mut el); - } - if attrs.contains("maxlength") { - self.update_max_width_attr(&el); - } - if attrs.contains("value") { - self.update_value_attr(&el); - } - } - if attrs.contains("value") { - let id = root.id(); - self.write_value(root.real_dom_mut(), id); - } - } - } - } -} - -impl RinkWidget for Password { - fn handle_event(&mut self, event: &crate::Event, node: dioxus_native_core::real_dom::NodeMut) { - match event.name { - "keydown" => { - if let EventData::Keyboard(data) = &event.data { - self.handle_keydown(node, data); - } - } - - "mousemove" => { - if let EventData::Mouse(data) = &event.data { - self.handle_mousemove(node, data); - } - } - - "mousedown" => { - if let EventData::Mouse(data) = &event.data { - self.handle_mousedown(node, data); - } - } - - "mouseup" => { - self.dragging = false; - } - - "mouseleave" => { - self.dragging = false; - } - - "mouseenter" => { - self.dragging = false; - } - - "focusout" => { - execute!(stdout(), MoveTo(0, 1000)).unwrap(); - } - - _ => {} - } +impl TextLikeController for PasswordController { + fn display_text(&self, text: &str) -> String { + text.chars().map(|_| '.').collect() } } diff --git a/packages/rink/src/widgets/text_like.rs b/packages/rink/src/widgets/text_like.rs new file mode 100644 index 000000000..b5874c776 --- /dev/null +++ b/packages/rink/src/widgets/text_like.rs @@ -0,0 +1,444 @@ +use std::{collections::HashMap, io::stdout}; + +use crossterm::{cursor::MoveTo, execute}; +use dioxus_html::{input_data::keyboard_types::Key, KeyboardData, MouseData}; +use dioxus_native_core::{ + custom_element::CustomElement, + node::OwnedAttributeDiscription, + node_ref::AttributeMask, + prelude::{ElementNode, NodeType}, + real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom}, + utils::cursor::{Cursor, Pos}, + NodeId, +}; +use shipyard::UniqueView; +use taffy::geometry::Point; + +use crate::{query::get_layout, Event, EventData, FormData, Query}; + +use super::{RinkWidget, WidgetContext}; + +pub(crate) trait TextLikeController { + fn display_text(&self, text: &str) -> String { + text.to_string() + } +} + +#[derive(Debug, Default)] +pub(crate) struct EmptyController; + +impl TextLikeController for EmptyController {} + +#[derive(Debug, Default)] +pub(crate) struct TextLike { + text: String, + div_wrapper: NodeId, + pre_cursor_text: NodeId, + highlighted_text: NodeId, + post_cursor_text: NodeId, + cursor: Cursor, + dragging: bool, + border: bool, + max_len: Option, + controller: C, +} + +impl TextLike { + fn width(el: &ElementNodeMut) -> String { + if let Some(value) = el + .get_attribute(&OwnedAttributeDiscription { + name: "width".to_string(), + namespace: Some("style".to_string()), + }) + .and_then(|value| value.as_text()) + .map(|value| value.to_string()) + { + value + } else { + "1px".to_string() + } + } + + fn height(el: &ElementNodeMut) -> String { + if let Some(value) = el + .get_attribute(&OwnedAttributeDiscription { + name: "height".to_string(), + namespace: Some("style".to_string()), + }) + .and_then(|value| value.as_text()) + .map(|value| value.to_string()) + { + value + } else { + "1px".to_string() + } + } + + fn update_max_width_attr(&mut self, el: &ElementNodeMut) { + if let Some(value) = el + .get_attribute(&OwnedAttributeDiscription { + name: "maxlength".to_string(), + namespace: None, + }) + .and_then(|value| value.as_text()) + .map(|value| value.to_string()) + { + if let Ok(max_len) = value.parse::() { + self.max_len = Some(max_len); + } + } + } + + fn update_size_attr(&mut self, el: &mut ElementNodeMut) { + let width = Self::width(el); + let height = Self::height(el); + let single_char = width + .strip_prefix("px") + .and_then(|n| n.parse::().ok().filter(|num| *num > 3)) + .is_some() + || height + .strip_prefix("px") + .and_then(|n| n.parse::().ok().filter(|num| *num > 3)) + .is_some(); + self.border = !single_char; + let border_style = if self.border { "solid" } else { "none" }; + el.set_attribute( + OwnedAttributeDiscription { + name: "border-style".to_string(), + namespace: Some("style".to_string()), + }, + border_style.to_string(), + ); + } + + fn update_value_attr(&mut self, el: &ElementNodeMut) { + if let Some(value) = el + .get_attribute(&OwnedAttributeDiscription { + name: "value".to_string(), + namespace: None, + }) + .and_then(|value| value.as_text()) + .map(|value| value.to_string()) + { + self.text = value; + } + } + + pub(crate) fn set_text(&mut self, text: String, rdom: &mut RealDom, id: NodeId) { + self.text = text; + self.write_value(rdom, id); + } + + pub(crate) fn text(&self) -> &str { + self.text.as_str() + } + + fn write_value(&self, rdom: &mut RealDom, id: NodeId) { + let start_highlight = self.cursor.first().idx(self.text.as_str()); + let end_highlight = self.cursor.last().idx(self.text.as_str()); + let (text_before_first_cursor, text_after_first_cursor) = + self.text.split_at(start_highlight); + let (text_highlighted, text_after_second_cursor) = + text_after_first_cursor.split_at(end_highlight - start_highlight); + + if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) { + let node_type = text.node_type_mut(); + let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; + *text.text_mut() = self.controller.display_text(text_before_first_cursor); + } + + if let Some(mut text) = rdom.get_mut(self.highlighted_text) { + let node_type = text.node_type_mut(); + let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; + *text.text_mut() = self.controller.display_text(text_highlighted); + } + + if let Some(mut text) = rdom.get_mut(self.post_cursor_text) { + let node_type = text.node_type_mut(); + let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; + *text.text_mut() = self.controller.display_text(text_after_second_cursor); + } + + // send the event + { + let world = rdom.raw_world_mut(); + let data: FormData = FormData { + value: self.text.clone(), + values: HashMap::new(), + files: None, + }; + let ctx: UniqueView = world.borrow().expect("expected widget context"); + + ctx.send(Event { + id, + name: "input", + data: EventData::Form(data), + bubbles: true, + }); + } + } + + fn handle_keydown(&mut self, mut root: NodeMut, data: &KeyboardData) { + let key = data.key(); + let modifiers = data.modifiers(); + let code = data.code(); + + if key == Key::Enter { + return; + } + self.cursor.handle_input( + &code, + &key, + &modifiers, + &mut self.text, + self.max_len.unwrap_or(1000), + ); + + let id = root.id(); + + let rdom = root.real_dom_mut(); + self.write_value(rdom, id); + let world = rdom.raw_world_mut(); + + // move cursor to new position + let taffy = { + let query: UniqueView = world.borrow().unwrap(); + query.stretch.clone() + }; + + let taffy = taffy.lock().unwrap(); + + let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap(); + let Point { x, y } = layout.location; + + let Pos { col, row } = self.cursor.start; + let (x, y) = (col as u16 + x as u16, row as u16 + y as u16); + if let Ok(pos) = crossterm::cursor::position() { + if pos != (x, y) { + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } else { + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } + + fn handle_mousemove(&mut self, mut root: NodeMut, data: &MouseData) { + if self.dragging { + let id = root.id(); + let offset = data.element_coordinates(); + let mut new = Pos::new(offset.x as usize, offset.y as usize); + + // textboxs are only one line tall + new.row = 0; + + if new != self.cursor.start { + self.cursor.end = Some(new); + } + let rdom = root.real_dom_mut(); + self.write_value(rdom, id); + } + } + + fn handle_mousedown(&mut self, mut root: NodeMut, data: &MouseData) { + let offset = data.element_coordinates(); + let mut new = Pos::new(offset.x as usize, offset.y as usize); + + // textboxs are only one line tall + new.row = 0; + + new.realize_col(self.text.as_str()); + self.cursor = Cursor::from_start(new); + self.dragging = true; + + let id = root.id(); + + // move cursor to new position + let rdom = root.real_dom_mut(); + let world = rdom.raw_world_mut(); + let taffy = { + let query: UniqueView = world.borrow().unwrap(); + query.stretch.clone() + }; + + let taffy = taffy.lock().unwrap(); + + let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap(); + let Point { x, y } = layout.location; + + let Pos { col, row } = self.cursor.start; + let (x, y) = (col as u16 + x as u16, row as u16 + y as u16); + if let Ok(pos) = crossterm::cursor::position() { + if pos != (x, y) { + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + } else { + execute!(stdout(), MoveTo(x, y)).unwrap(); + } + + self.write_value(rdom, id) + } +} + +impl CustomElement for TextLike { + const NAME: &'static str = "input"; + + fn roots(&self) -> Vec { + vec![self.div_wrapper] + } + + fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { + let node_type = root.node_type(); + let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; + + let value = el + .attributes + .get(&OwnedAttributeDiscription { + name: "value".to_string(), + namespace: None, + }) + .and_then(|value| value.as_text()) + .map(|value| value.to_string()); + + drop(node_type); + + let rdom = root.real_dom_mut(); + + let pre_text = rdom.create_node(String::new()); + let pre_text_id = pre_text.id(); + let highlighted_text = rdom.create_node(String::new()); + let highlighted_text_id = highlighted_text.id(); + let mut highlighted_text_span = rdom.create_node(NodeType::Element(ElementNode { + tag: "span".to_string(), + attributes: [( + OwnedAttributeDiscription { + name: "background-color".to_string(), + namespace: Some("style".to_string()), + }, + "rgba(255, 255, 255, 50%)".to_string().into(), + )] + .into_iter() + .collect(), + ..Default::default() + })); + highlighted_text_span.add_child(highlighted_text_id); + let highlighted_text_span_id = highlighted_text_span.id(); + let post_text = rdom.create_node(value.clone().unwrap_or_default()); + let post_text_id = post_text.id(); + let mut div_wrapper = rdom.create_node(NodeType::Element(ElementNode { + tag: "div".to_string(), + attributes: [( + OwnedAttributeDiscription { + name: "display".to_string(), + namespace: Some("style".to_string()), + }, + "flex".to_string().into(), + )] + .into_iter() + .collect(), + ..Default::default() + })); + let div_wrapper_id = div_wrapper.id(); + div_wrapper.add_child(pre_text_id); + div_wrapper.add_child(highlighted_text_span_id); + div_wrapper.add_child(post_text_id); + + div_wrapper.add_event_listener("mousemove"); + div_wrapper.add_event_listener("mousedown"); + div_wrapper.add_event_listener("mouseup"); + div_wrapper.add_event_listener("mouseleave"); + div_wrapper.add_event_listener("mouseenter"); + root.add_event_listener("keydown"); + root.add_event_listener("focusout"); + + Self { + pre_cursor_text: pre_text_id, + highlighted_text: highlighted_text_id, + post_cursor_text: post_text_id, + div_wrapper: div_wrapper_id, + cursor: Cursor::default(), + text: value.unwrap_or_default(), + ..Default::default() + } + } + + fn attributes_changed( + &mut self, + mut root: dioxus_native_core::real_dom::NodeMut, + attributes: &dioxus_native_core::node_ref::AttributeMask, + ) { + match attributes { + AttributeMask::All => { + { + let node_type = root.node_type_mut(); + let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + self.update_value_attr(&el); + self.update_size_attr(&mut el); + self.update_max_width_attr(&el); + } + let id = root.id(); + self.write_value(root.real_dom_mut(), id); + } + AttributeMask::Some(attrs) => { + { + let node_type = root.node_type_mut(); + let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; + if attrs.contains("width") || attrs.contains("height") { + self.update_size_attr(&mut el); + } + if attrs.contains("maxlength") { + self.update_max_width_attr(&el); + } + if attrs.contains("value") { + self.update_value_attr(&el); + } + } + if attrs.contains("value") { + let id = root.id(); + self.write_value(root.real_dom_mut(), id); + } + } + } + } +} + +impl RinkWidget for TextLike { + fn handle_event(&mut self, event: &crate::Event, node: NodeMut) { + match event.name { + "keydown" => { + if let EventData::Keyboard(data) = &event.data { + self.handle_keydown(node, data); + } + } + + "mousemove" => { + if let EventData::Mouse(data) = &event.data { + self.handle_mousemove(node, data); + } + } + + "mousedown" => { + if let EventData::Mouse(data) = &event.data { + self.handle_mousedown(node, data); + } + } + + "mouseup" => { + self.dragging = false; + } + + "mouseleave" => { + self.dragging = false; + } + + "mouseenter" => { + self.dragging = false; + } + + "focusout" => { + execute!(stdout(), MoveTo(0, 1000)).unwrap(); + } + + _ => {} + } + } +} diff --git a/packages/rink/src/widgets/textbox.rs b/packages/rink/src/widgets/textbox.rs index 8eaadb7a7..df9bd7758 100644 --- a/packages/rink/src/widgets/textbox.rs +++ b/packages/rink/src/widgets/textbox.rs @@ -1,423 +1,3 @@ -use std::{collections::HashMap, io::stdout}; +use super::text_like::TextLike; -use crossterm::{cursor::MoveTo, execute}; -use dioxus_html::{input_data::keyboard_types::Key, KeyboardData, MouseData}; -use dioxus_native_core::{ - custom_element::CustomElement, - node::OwnedAttributeDiscription, - node_ref::AttributeMask, - prelude::{ElementNode, NodeType}, - real_dom::{ElementNodeMut, NodeImmutable, NodeMut, NodeTypeMut, RealDom}, - utils::cursor::{Cursor, Pos}, - NodeId, -}; -use shipyard::UniqueView; -use taffy::geometry::Point; - -use crate::{query::get_layout, Event, EventData, FormData, Query}; - -use super::{RinkWidget, WidgetContext}; - -#[derive(Debug, Default)] -pub(crate) struct TextBox { - text: String, - div_wrapper: NodeId, - pre_cursor_text: NodeId, - highlighted_text: NodeId, - post_cursor_text: NodeId, - cursor: Cursor, - dragging: bool, - border: bool, - max_len: Option, -} - -impl TextBox { - fn width(el: &ElementNodeMut) -> String { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "width".to_string(), - namespace: Some("style".to_string()), - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - value - } else { - "1px".to_string() - } - } - - fn height(el: &ElementNodeMut) -> String { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "height".to_string(), - namespace: Some("style".to_string()), - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - value - } else { - "1px".to_string() - } - } - - fn update_max_width_attr(&mut self, el: &ElementNodeMut) { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "maxlength".to_string(), - namespace: None, - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - if let Ok(max_len) = value.parse::() { - self.max_len = Some(max_len); - } - } - } - - fn update_size_attr(&mut self, el: &mut ElementNodeMut) { - let width = Self::width(el); - let height = Self::height(el); - let single_char = width - .strip_prefix("px") - .and_then(|n| n.parse::().ok().filter(|num| *num > 3)) - .is_some() - || height - .strip_prefix("px") - .and_then(|n| n.parse::().ok().filter(|num| *num > 3)) - .is_some(); - self.border = !single_char; - let border_style = if self.border { "solid" } else { "none" }; - el.set_attribute( - OwnedAttributeDiscription { - name: "border-style".to_string(), - namespace: Some("style".to_string()), - }, - border_style.to_string(), - ); - } - - fn update_value_attr(&mut self, el: &ElementNodeMut) { - if let Some(value) = el - .get_attribute(&OwnedAttributeDiscription { - name: "value".to_string(), - namespace: None, - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()) - { - self.text = value; - } - } - - fn write_value(&self, rdom: &mut RealDom, id: NodeId) { - let start_highlight = self.cursor.first().idx(self.text.as_str()); - let end_highlight = self.cursor.last().idx(self.text.as_str()); - let (text_before_first_cursor, text_after_first_cursor) = - self.text.split_at(start_highlight); - let (text_highlighted, text_after_second_cursor) = - text_after_first_cursor.split_at(end_highlight - start_highlight); - - if let Some(mut text) = rdom.get_mut(self.pre_cursor_text) { - let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; - *text.text_mut() = text_before_first_cursor.to_string(); - } - - if let Some(mut text) = rdom.get_mut(self.highlighted_text) { - let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; - *text.text_mut() = text_highlighted.to_string(); - } - - if let Some(mut text) = rdom.get_mut(self.post_cursor_text) { - let node_type = text.node_type_mut(); - let NodeTypeMut::Text(mut text) = node_type else { panic!("input must be an element") }; - *text.text_mut() = text_after_second_cursor.to_string(); - } - - // send the event - { - let world = rdom.raw_world_mut(); - let data = FormData { - value: self.text.clone(), - values: HashMap::new(), - files: None, - }; - let ctx: UniqueView = world.borrow().expect("expected widget context"); - - ctx.send(Event { - id, - name: "input", - data: EventData::Form(data), - bubbles: true, - }); - } - } - - fn handle_keydown(&mut self, mut root: NodeMut, data: &KeyboardData) { - let key = data.key(); - let modifiers = data.modifiers(); - let code = data.code(); - - if key == Key::Enter { - return; - } - self.cursor.handle_input( - &code, - &key, - &modifiers, - &mut self.text, - self.max_len.unwrap_or(1000), - ); - - let id = root.id(); - - let rdom = root.real_dom_mut(); - self.write_value(rdom, id); - let world = rdom.raw_world_mut(); - - // move cursor to new position - let taffy = { - let query: UniqueView = world.borrow().unwrap(); - query.stretch.clone() - }; - - let taffy = taffy.lock().unwrap(); - - let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap(); - let Point { x, y } = layout.location; - - let Pos { col, row } = self.cursor.start; - let (x, y) = (col as u16 + x as u16, row as u16 + y as u16); - if let Ok(pos) = crossterm::cursor::position() { - if pos != (x, y) { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - } else { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - } - - fn handle_mousemove(&mut self, mut root: NodeMut, data: &MouseData) { - if self.dragging { - let id = root.id(); - let offset = data.element_coordinates(); - let mut new = Pos::new(offset.x as usize, offset.y as usize); - - // textboxs are only one line tall - new.row = 0; - - if new != self.cursor.start { - self.cursor.end = Some(new); - } - let rdom = root.real_dom_mut(); - self.write_value(rdom, id); - } - } - - fn handle_mousedown(&mut self, mut root: NodeMut, data: &MouseData) { - let offset = data.element_coordinates(); - let mut new = Pos::new(offset.x as usize, offset.y as usize); - - // textboxs are only one line tall - new.row = 0; - - new.realize_col(self.text.as_str()); - self.cursor = Cursor::from_start(new); - self.dragging = true; - - let id = root.id(); - - // move cursor to new position - let rdom = root.real_dom_mut(); - let world = rdom.raw_world_mut(); - let taffy = { - let query: UniqueView = world.borrow().unwrap(); - query.stretch.clone() - }; - - let taffy = taffy.lock().unwrap(); - - let layout = get_layout(rdom.get(self.div_wrapper).unwrap(), &taffy).unwrap(); - let Point { x, y } = layout.location; - - let Pos { col, row } = self.cursor.start; - let (x, y) = (col as u16 + x as u16, row as u16 + y as u16); - if let Ok(pos) = crossterm::cursor::position() { - if pos != (x, y) { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - } else { - execute!(stdout(), MoveTo(x, y)).unwrap(); - } - - self.write_value(rdom, id) - } -} - -impl CustomElement for TextBox { - const NAME: &'static str = "input"; - - fn roots(&self) -> Vec { - vec![self.div_wrapper] - } - - fn create(mut root: dioxus_native_core::real_dom::NodeMut) -> Self { - let node_type = root.node_type(); - let NodeType::Element(el) = &*node_type else { panic!("input must be an element") }; - - let value = el - .attributes - .get(&OwnedAttributeDiscription { - name: "value".to_string(), - namespace: None, - }) - .and_then(|value| value.as_text()) - .map(|value| value.to_string()); - - drop(node_type); - - let rdom = root.real_dom_mut(); - - let pre_text = rdom.create_node(String::new()); - let pre_text_id = pre_text.id(); - let highlighted_text = rdom.create_node(String::new()); - let highlighted_text_id = highlighted_text.id(); - let mut highlighted_text_span = rdom.create_node(NodeType::Element(ElementNode { - tag: "span".to_string(), - attributes: [( - OwnedAttributeDiscription { - name: "background-color".to_string(), - namespace: Some("style".to_string()), - }, - "rgba(255, 255, 255, 50%)".to_string().into(), - )] - .into_iter() - .collect(), - ..Default::default() - })); - highlighted_text_span.add_child(highlighted_text_id); - let highlighted_text_span_id = highlighted_text_span.id(); - let post_text = rdom.create_node(value.clone().unwrap_or_default()); - let post_text_id = post_text.id(); - let mut div_wrapper = rdom.create_node(NodeType::Element(ElementNode { - tag: "div".to_string(), - attributes: [( - OwnedAttributeDiscription { - name: "display".to_string(), - namespace: Some("style".to_string()), - }, - "flex".to_string().into(), - )] - .into_iter() - .collect(), - ..Default::default() - })); - let div_wrapper_id = div_wrapper.id(); - div_wrapper.add_child(pre_text_id); - div_wrapper.add_child(highlighted_text_span_id); - div_wrapper.add_child(post_text_id); - - div_wrapper.add_event_listener("mousemove"); - div_wrapper.add_event_listener("mousedown"); - div_wrapper.add_event_listener("mouseup"); - div_wrapper.add_event_listener("mouseleave"); - div_wrapper.add_event_listener("mouseenter"); - root.add_event_listener("keydown"); - root.add_event_listener("focusout"); - - Self { - pre_cursor_text: pre_text_id, - highlighted_text: highlighted_text_id, - post_cursor_text: post_text_id, - div_wrapper: div_wrapper_id, - cursor: Cursor::default(), - text: value.unwrap_or_default(), - ..Default::default() - } - } - - fn attributes_changed( - &mut self, - mut root: dioxus_native_core::real_dom::NodeMut, - attributes: &dioxus_native_core::node_ref::AttributeMask, - ) { - match attributes { - AttributeMask::All => { - { - let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; - self.update_value_attr(&el); - self.update_size_attr(&mut el); - self.update_max_width_attr(&el); - } - let id = root.id(); - self.write_value(root.real_dom_mut(), id); - } - AttributeMask::Some(attrs) => { - { - let node_type = root.node_type_mut(); - let NodeTypeMut::Element(mut el) = node_type else { panic!("input must be an element") }; - if attrs.contains("width") || attrs.contains("height") { - self.update_size_attr(&mut el); - } - if attrs.contains("maxlength") { - self.update_max_width_attr(&el); - } - if attrs.contains("value") { - self.update_value_attr(&el); - } - } - if attrs.contains("value") { - let id = root.id(); - self.write_value(root.real_dom_mut(), id); - } - } - } - } -} - -impl RinkWidget for TextBox { - fn handle_event(&mut self, event: &crate::Event, node: NodeMut) { - match event.name { - "keydown" => { - if let EventData::Keyboard(data) = &event.data { - self.handle_keydown(node, data); - } - } - - "mousemove" => { - if let EventData::Mouse(data) = &event.data { - self.handle_mousemove(node, data); - } - } - - "mousedown" => { - if let EventData::Mouse(data) = &event.data { - self.handle_mousedown(node, data); - } - } - - "mouseup" => { - self.dragging = false; - } - - "mouseleave" => { - self.dragging = false; - } - - "mouseenter" => { - self.dragging = false; - } - - "focusout" => { - execute!(stdout(), MoveTo(0, 1000)).unwrap(); - } - - _ => {} - } - } -} +pub(crate) type TextBox = TextLike;