//! Display fields that can be filled with text. //! //! A [`TextInput`] has some local [`State`]. //! //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html use crate::{ input::{keyboard, mouse, ButtonState}, layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; /// A widget that can be filled with text by using a keyboard. pub struct TextInput<'a, Message> { state: &'a mut State, placeholder: String, value: Value, width: Length, max_width: Length, padding: u16, size: Option, on_change: Box Message>, on_submit: Option, } impl<'a, Message> TextInput<'a, Message> { /// Creates a new [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html pub fn new( state: &'a mut State, placeholder: &str, value: &str, on_change: F, ) -> Self where F: 'static + Fn(String) -> Message, { Self { state, placeholder: String::from(placeholder), value: Value::new(value), width: Length::Fill, max_width: Length::Shrink, padding: 0, size: None, on_change: Box::new(on_change), on_submit: None, } } /// Sets the width of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html pub fn width(mut self, width: Length) -> Self { self.width = width; self } /// Sets the maximum width of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html pub fn max_width(mut self, max_width: Length) -> Self { self.max_width = max_width; self } /// Sets the padding of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html pub fn padding(mut self, units: u16) -> Self { self.padding = units; self } /// Sets the text size of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html pub fn size(mut self, size: u16) -> Self { self.size = Some(size); self } /// Sets the message that should be produced when the [`TextInput`] is /// focused and the enter key is pressed. /// /// [`TextInput`]: struct.TextInput.html pub fn on_submit(mut self, message: Message) -> Self { self.on_submit = Some(message); self } } impl<'a, Message, Renderer> Widget for TextInput<'a, Message> where Renderer: self::Renderer, Message: Clone + std::fmt::Debug, { fn width(&self) -> Length { self.width } fn layout( &self, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let padding = self.padding as f32; let text_size = self.size.unwrap_or(renderer.default_size()); let limits = limits .pad(padding) .width(self.width) .height(Length::Units(text_size)); let mut text = layout::Node::new(limits.resolve(Size::ZERO)); text.bounds.x = padding; text.bounds.y = padding; layout::Node::with_children(text.size().pad(padding), vec![text]) } fn on_event( &mut self, event: Event, layout: Layout<'_>, cursor_position: Point, messages: &mut Vec, _renderer: &Renderer, ) { match event { Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, state: ButtonState::Pressed, }) => { self.state.is_focused = layout.bounds().contains(cursor_position); if self.state.cursor_position(&self.value) == 0 { self.state.move_cursor_to_end(&self.value); } } Event::Keyboard(keyboard::Event::CharacterReceived(c)) if self.state.is_focused && !c.is_control() => { let cursor_position = self.state.cursor_position(&self.value); self.value.insert(cursor_position, c); self.state.move_cursor_right(&self.value); let message = (self.on_change)(self.value.to_string()); messages.push(message); } Event::Keyboard(keyboard::Event::Input { key_code, state: ButtonState::Pressed, }) if self.state.is_focused => match key_code { keyboard::KeyCode::Enter => { if let Some(on_submit) = self.on_submit.clone() { messages.push(on_submit); } } keyboard::KeyCode::Backspace => { let cursor_position = self.state.cursor_position(&self.value); if cursor_position > 0 { self.state.move_cursor_left(&self.value); let _ = self.value.remove(cursor_position - 1); let message = (self.on_change)(self.value.to_string()); messages.push(message); } } keyboard::KeyCode::Delete => { let cursor_position = self.state.cursor_position(&self.value); if cursor_position < self.value.len() { let _ = self.value.remove(cursor_position); let message = (self.on_change)(self.value.to_string()); messages.push(message); } } keyboard::KeyCode::Left => { self.state.move_cursor_left(&self.value); } keyboard::KeyCode::Right => { self.state.move_cursor_right(&self.value); } _ => {} }, _ => {} } } fn draw( &self, renderer: &mut Renderer, layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { let bounds = layout.bounds(); let text_bounds = layout.children().next().unwrap().bounds(); renderer.draw( bounds, text_bounds, cursor_position, self.size.unwrap_or(renderer.default_size()), &self.placeholder, &self.value, &self.state, ) } fn hash_layout(&self, state: &mut Hasher) { use std::{any::TypeId, hash::Hash}; TypeId::of::>().hash(state); self.width.hash(state); self.max_width.hash(state); self.padding.hash(state); self.size.hash(state); } } pub trait Renderer: crate::Renderer + Sized { fn default_size(&self) -> u16; fn draw( &mut self, bounds: Rectangle, text_bounds: Rectangle, cursor_position: Point, size: u16, placeholder: &str, value: &Value, state: &State, ) -> Self::Output; } impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Renderer: 'static + self::Renderer, Message: 'static + Clone + std::fmt::Debug, { fn from( text_input: TextInput<'a, Message>, ) -> Element<'a, Message, Renderer> { Element::new(text_input) } } /// The state of a [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html #[derive(Debug, Default, Clone)] pub struct State { is_focused: bool, cursor_position: usize, } impl State { /// Creates a new [`State`], representing an unfocused [`TextInput`]. /// /// [`State`]: struct.State.html pub fn new() -> Self { Self::default() } /// Creates a new [`State`], representing a focused [`TextInput`]. /// /// [`State`]: struct.State.html pub fn focused() -> Self { use std::usize; Self { is_focused: true, cursor_position: usize::MAX, } } pub fn is_focused(&self) -> bool { self.is_focused } /// Returns the cursor position of a [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html pub fn cursor_position(&self, value: &Value) -> usize { self.cursor_position.min(value.len()) } /// Moves the cursor of a [`TextInput`] to the right. /// /// [`TextInput`]: struct.TextInput.html pub fn move_cursor_right(&mut self, value: &Value) { let current = self.cursor_position(value); if current < value.len() { self.cursor_position = current + 1; } } /// Moves the cursor of a [`TextInput`] to the left. /// /// [`TextInput`]: struct.TextInput.html pub fn move_cursor_left(&mut self, value: &Value) { let current = self.cursor_position(value); if current > 0 { self.cursor_position = current - 1; } } /// Moves the cursor of a [`TextInput`] to the end. /// /// [`TextInput`]: struct.TextInput.html pub fn move_cursor_to_end(&mut self, value: &Value) { self.cursor_position = value.len(); } } /// The value of a [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html // TODO: Use `unicode-segmentation` #[derive(Debug)] pub struct Value(Vec); impl Value { /// Creates a new [`Value`] from a string slice. /// /// [`Value`]: struct.Value.html pub fn new(string: &str) -> Self { Self(string.chars().collect()) } /// Returns the total amount of `char` in the [`Value`]. /// /// [`Value`]: struct.Value.html pub fn len(&self) -> usize { self.0.len() } /// Returns a new [`Value`] containing the `char` until the given `index`. /// /// [`Value`]: struct.Value.html pub fn until(&self, index: usize) -> Self { Self(self.0[..index.min(self.len())].to_vec()) } /// Converts the [`Value`] into a `String`. /// /// [`Value`]: struct.Value.html pub fn to_string(&self) -> String { use std::iter::FromIterator; String::from_iter(self.0.iter()) } /// Inserts a new `char` at the given `index`. /// /// [`Value`]: struct.Value.html pub fn insert(&mut self, index: usize, c: char) { self.0.insert(index, c); } /// Removes the `char` at the given `index`. /// /// [`Value`]: struct.Value.html pub fn remove(&mut self, index: usize) { let _ = self.0.remove(index); } }