summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2019-10-31 03:50:40 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2019-10-31 03:50:40 +0100
commit51a0e99097f9ecb63eeb7f2ea7c38089977eb4d0 (patch)
treed6532ec8c98b80fa72e3b78a556ccf1b26b33c02
parent374b54c3ecbe39a24cfa6b8eccb9b2a2098f65c7 (diff)
downloadiced-51a0e99097f9ecb63eeb7f2ea7c38089977eb4d0.tar.gz
iced-51a0e99097f9ecb63eeb7f2ea7c38089977eb4d0.tar.bz2
iced-51a0e99097f9ecb63eeb7f2ea7c38089977eb4d0.zip
Implement cursor movement in `TextInput`
-rw-r--r--core/src/widget/text_input.rs92
-rw-r--r--examples/todos.rs40
-rw-r--r--native/src/widget/text_input.rs57
-rw-r--r--wgpu/src/renderer/text_input.rs35
4 files changed, 175 insertions, 49 deletions
diff --git a/core/src/widget/text_input.rs b/core/src/widget/text_input.rs
index e0f0744b..95064ab3 100644
--- a/core/src/widget/text_input.rs
+++ b/core/src/widget/text_input.rs
@@ -1,9 +1,11 @@
use crate::Length;
+use std::ops::{Index, RangeTo};
+
pub struct TextInput<'a, Message> {
pub state: &'a mut State,
pub placeholder: String,
- pub value: String,
+ pub value: Value,
pub width: Length,
pub max_width: Length,
pub padding: u16,
@@ -12,11 +14,6 @@ pub struct TextInput<'a, Message> {
pub on_submit: Option<Message>,
}
-#[derive(Debug, Default)]
-pub struct State {
- pub is_focused: bool,
-}
-
impl<'a, Message> TextInput<'a, Message> {
pub fn new<F>(
state: &'a mut State,
@@ -30,7 +27,7 @@ impl<'a, Message> TextInput<'a, Message> {
Self {
state,
placeholder: String::from(placeholder),
- value: String::from(value),
+ value: Value::new(value),
width: Length::Fill,
max_width: Length::Shrink,
padding: 0,
@@ -84,3 +81,84 @@ where
f.debug_struct("TextInput").finish()
}
}
+
+#[derive(Debug, Default)]
+pub struct State {
+ pub is_focused: bool,
+ cursor_position: usize,
+}
+
+impl State {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn focused() -> Self {
+ Self {
+ is_focused: true,
+ ..Self::default()
+ }
+ }
+
+ pub fn move_cursor_right(&mut self, value: &Value) {
+ let current = self.cursor_position(value);
+
+ if current < value.len() {
+ self.cursor_position = current + 1;
+ }
+ }
+
+ pub fn move_cursor_left(&mut self, value: &Value) {
+ let current = self.cursor_position(value);
+
+ if current > 0 {
+ self.cursor_position = current - 1;
+ }
+ }
+
+ pub fn cursor_position(&self, value: &Value) -> usize {
+ self.cursor_position.min(value.len())
+ }
+}
+
+pub struct Value(Vec<char>);
+
+impl Value {
+ pub fn new(string: &str) -> Self {
+ Self(string.chars().collect())
+ }
+
+ pub fn len(&self) -> usize {
+ self.0.len()
+ }
+
+ pub fn until(&self, index: usize) -> Self {
+ Self(self.0[..index].iter().cloned().collect())
+ }
+
+ pub fn to_string(&self) -> String {
+ let mut string = String::new();
+
+ for c in self.0.iter() {
+ string.push(*c);
+ }
+
+ string
+ }
+
+ pub fn insert(&mut self, index: usize, c: char) {
+ self.0.insert(index, c);
+ }
+
+ pub fn remove(&mut self, index: usize) {
+ self.0.remove(index);
+ }
+}
+
+impl Index<RangeTo<usize>> for Value {
+ type Output = [char];
+
+ fn index(&self, index: RangeTo<usize>) -> &[char] {
+ &self.0[index]
+ }
+}
diff --git a/examples/todos.rs b/examples/todos.rs
index 6189c8db..9581262a 100644
--- a/examples/todos.rs
+++ b/examples/todos.rs
@@ -15,26 +15,6 @@ struct Todos {
tasks: Vec<Task>,
}
-#[derive(Debug)]
-struct Task {
- description: String,
- completed: bool,
-}
-
-impl Task {
- fn new(description: String) -> Self {
- Task {
- description,
- completed: false,
- }
- }
-
- fn view(&mut self) -> Element<bool> {
- Checkbox::new(self.completed, &self.description, |checked| checked)
- .into()
- }
-}
-
#[derive(Debug, Clone)]
pub enum Message {
InputChanged(String),
@@ -107,6 +87,26 @@ impl Application for Todos {
}
}
+#[derive(Debug)]
+struct Task {
+ description: String,
+ completed: bool,
+}
+
+impl Task {
+ fn new(description: String) -> Self {
+ Task {
+ description,
+ completed: false,
+ }
+ }
+
+ fn view(&mut self) -> Element<bool> {
+ Checkbox::new(self.completed, &self.description, |checked| checked)
+ .into()
+ }
+}
+
// Colors
const GRAY: Color = Color {
r: 0.5,
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index 976ca995..d9837b61 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -45,28 +45,55 @@ where
Event::Keyboard(keyboard::Event::CharacterReceived(c))
if self.state.is_focused && !c.is_control() =>
{
- self.value.push(c);
+ let cursor_position = self.state.cursor_position(&self.value);
- let message = (self.on_change)(self.value.clone());
- messages.push(message);
- }
- Event::Keyboard(keyboard::Event::Input {
- key_code: keyboard::KeyCode::Backspace,
- state: ButtonState::Pressed,
- }) => {
- let _ = self.value.pop();
+ self.value.insert(cursor_position, c);
+ self.state.move_cursor_right(&self.value);
- let message = (self.on_change)(self.value.clone());
+ let message = (self.on_change)(self.value.to_string());
messages.push(message);
}
Event::Keyboard(keyboard::Event::Input {
- key_code: keyboard::KeyCode::Enter,
+ key_code,
state: ButtonState::Pressed,
- }) => {
- if let Some(on_submit) = self.on_submit.clone() {
- messages.push(on_submit);
+ }) 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);
+ }
+ _ => {}
+ },
_ => {}
}
}
diff --git a/wgpu/src/renderer/text_input.rs b/wgpu/src/renderer/text_input.rs
index 30e06878..f119ae6a 100644
--- a/wgpu/src/renderer/text_input.rs
+++ b/wgpu/src/renderer/text_input.rs
@@ -55,17 +55,18 @@ impl text_input::Renderer for Renderer {
};
let size = f32::from(text_input.size.unwrap_or(self.default_size()));
+ let text = text_input.value.to_string();
let value = Primitive::Clip {
bounds: text_bounds,
offset: 0,
content: Box::new(Primitive::Text {
- content: if text_input.value.is_empty() {
+ content: if text.is_empty() {
text_input.placeholder.clone()
} else {
- text_input.value.clone()
+ text.clone()
},
- color: if text_input.value.is_empty() {
+ color: if text.is_empty() {
Color {
r: 0.7,
g: 0.7,
@@ -95,11 +96,18 @@ impl text_input::Renderer for Renderer {
primitives: if text_input.state.is_focused {
use wgpu_glyph::{GlyphCruncher, Scale, Section};
+ let text_before_cursor = &text_input
+ .value
+ .until(
+ text_input.state.cursor_position(&text_input.value),
+ )
+ .to_string();
+
let mut text_value_width = self
.glyph_brush
.borrow_mut()
.glyph_bounds(Section {
- text: &text_input.value,
+ text: text_before_cursor,
bounds: (f32::INFINITY, text_bounds.height),
scale: Scale { x: size, y: size },
..Default::default()
@@ -107,11 +115,24 @@ impl text_input::Renderer for Renderer {
.map(|bounds| bounds.width().round())
.unwrap_or(0.0);
- let spaces_at_the_end = text_input.value.len()
- - text_input.value.trim_end().len();
+ let spaces_at_the_end = text_before_cursor.len()
+ - text_before_cursor.trim_end().len();
if spaces_at_the_end > 0 {
- text_value_width += spaces_at_the_end as f32 * 5.0;
+ let space_width = {
+ let glyph_brush = self.glyph_brush.borrow();
+
+ // TODO: Select appropriate font
+ let font = &glyph_brush.fonts()[0];
+
+ font.glyph(' ')
+ .scaled(Scale { x: size, y: size })
+ .h_metrics()
+ .advance_width
+ };
+
+ text_value_width +=
+ spaces_at_the_end as f32 * space_width;
}
let cursor = Primitive::Quad {