Implement cursor movement in TextInput
This commit is contained in:
parent
374b54c3ec
commit
51a0e99097
4 changed files with 176 additions and 50 deletions
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
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: keyboard::KeyCode::Backspace,
|
||||
key_code,
|
||||
state: ButtonState::Pressed,
|
||||
}) => {
|
||||
let _ = self.value.pop();
|
||||
|
||||
let message = (self.on_change)(self.value.clone());
|
||||
messages.push(message);
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::Input {
|
||||
key_code: keyboard::KeyCode::Enter,
|
||||
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);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue