Improve text_input::cursor API

This commit is contained in:
Héctor Ramón Jiménez 2020-03-24 20:23:31 +01:00
parent 763f64b653
commit 6b89dd7db9
4 changed files with 170 additions and 141 deletions

View File

@ -34,7 +34,7 @@
//! [`window::Renderer`]: window/trait.Renderer.html //! [`window::Renderer`]: window/trait.Renderer.html
//! [`UserInterface`]: struct.UserInterface.html //! [`UserInterface`]: struct.UserInterface.html
//! [renderer]: renderer/index.html //! [renderer]: renderer/index.html
#![deny(missing_docs)] //#![deny(missing_docs)]
#![deny(missing_debug_implementations)] #![deny(missing_debug_implementations)]
#![deny(unused_results)] #![deny(unused_results)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]

View File

@ -4,17 +4,18 @@
//! //!
//! [`TextInput`]: struct.TextInput.html //! [`TextInput`]: struct.TextInput.html
//! [`State`]: struct.State.html //! [`State`]: struct.State.html
mod cursor; pub mod cursor;
pub use cursor::Cursor;
use crate::{ use crate::{
input::{ input::{
keyboard, keyboard,
mouse::{self, click}, mouse::{self, click},
ButtonState, ButtonState,
}, },
layout, layout, Clipboard, Element, Event, Font, Hasher, Layout, Length, Point,
widget::text_input::cursor::Cursor, Rectangle, Size, Widget,
Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, Rectangle,
Size, Widget,
}; };
use std::u32; use std::u32;
@ -261,7 +262,8 @@ where
if self.is_secure { if self.is_secure {
self.state.cursor.select_all(&self.value); self.state.cursor.select_all(&self.value);
} else { } else {
let end = self.state.cursor.end(); let end = self.state.cursor.end(&self.value);
self.state.cursor.select_range( self.state.cursor.select_range(
self.value.previous_start_of_word(end), self.value.previous_start_of_word(end),
self.value.next_end_of_word(end), self.value.next_end_of_word(end),
@ -307,7 +309,7 @@ where
self.font, self.font,
); );
let pos = find_cursor_position( let position = find_cursor_position(
renderer, renderer,
target + offset, target + offset,
&value, &value,
@ -317,9 +319,10 @@ where
self.font, self.font,
); );
self.state self.state.cursor.select_range(
.cursor self.state.cursor.start(&value),
.select_range(self.state.cursor.start(), pos); position,
);
} }
} }
} }
@ -328,15 +331,15 @@ where
&& self.state.is_pasting.is_none() && self.state.is_pasting.is_none()
&& !c.is_control() => && !c.is_control() =>
{ {
match self.state.cursor.selection_position() { match self.state.cursor.selection() {
Some((left, right)) => { Some((left, right)) => {
self.value.remove_many(left, right); self.value.remove_many(left, right);
self.state.cursor.move_left(); self.state.cursor.move_left(&self.value);
} }
_ => (), _ => (),
} }
self.value
.insert(self.state.cursor.end().min(self.value.len()), c); self.value.insert(self.state.cursor.end(&self.value), c);
self.state.cursor.move_right(&self.value); self.state.cursor.move_right(&self.value);
let message = (self.on_change)(self.value.to_string()); let message = (self.on_change)(self.value.to_string());
@ -353,19 +356,18 @@ where
} }
} }
keyboard::KeyCode::Backspace => { keyboard::KeyCode::Backspace => {
match self.state.cursor.selection_position() { match self.state.cursor.selection() {
Some((start, end)) => { Some((start, end)) => {
self.value.remove_many(start, end); self.value.remove_many(start, end);
self.state.cursor.move_left(); self.state.cursor.move_left(&self.value);
} }
None => { None => {
if self.state.cursor.start().min(self.value.len()) if self.state.cursor.start(&self.value) > 0 {
> 0 self.state.cursor.move_left(&self.value);
{
self.state.cursor.move_left(); let _ = self.value.remove(
let _ = self self.state.cursor.start(&self.value),
.value );
.remove(self.state.cursor.start());
} }
} }
} }
@ -373,15 +375,16 @@ where
messages.push(message); messages.push(message);
} }
keyboard::KeyCode::Delete => { keyboard::KeyCode::Delete => {
match self.state.cursor.selection_position() { match self.state.cursor.selection() {
Some((start, end)) => { Some((start, end)) => {
self.value.remove_many(start, end); self.value.remove_many(start, end);
self.state.cursor.move_left(); self.state.cursor.move_left(&self.value);
} }
None => { None => {
if self.state.cursor.end() < self.value.len() { let end = self.state.cursor.end(&self.value);
let _ =
self.value.remove(self.state.cursor.end()); if end > 0 {
let _ = self.value.remove(end);
} }
} }
} }
@ -398,9 +401,9 @@ where
self.state.cursor.move_left_by_words(&self.value); self.state.cursor.move_left_by_words(&self.value);
} }
} else if modifiers.shift { } else if modifiers.shift {
self.state.cursor.select_left() self.state.cursor.select_left(&self.value)
} else { } else {
self.state.cursor.move_left(); self.state.cursor.move_left(&self.value);
} }
} }
keyboard::KeyCode::Right => { keyboard::KeyCode::Right => {
@ -422,9 +425,10 @@ where
} }
keyboard::KeyCode::Home => { keyboard::KeyCode::Home => {
if modifiers.shift { if modifiers.shift {
self.state self.state.cursor.select_range(
.cursor self.state.cursor.start(&self.value),
.select_range(self.state.cursor.start(), 0); 0,
);
} else { } else {
self.state.cursor.move_to(0); self.state.cursor.move_to(0);
} }
@ -432,7 +436,7 @@ where
keyboard::KeyCode::End => { keyboard::KeyCode::End => {
if modifiers.shift { if modifiers.shift {
self.state.cursor.select_range( self.state.cursor.select_range(
self.state.cursor.start(), self.state.cursor.start(&self.value),
self.value.len(), self.value.len(),
); );
} else { } else {
@ -456,16 +460,16 @@ where
} }
}; };
match self.state.cursor.selection_position() { match self.state.cursor.selection() {
Some((left, right)) => { Some((left, right)) => {
self.value.remove_many(left, right); self.value.remove_many(left, right);
self.state.cursor.move_left(); self.state.cursor.move_left(&self.value);
} }
_ => (), _ => (),
} }
self.value.insert_many( self.value.insert_many(
self.state.cursor.end().min(self.value.len()), self.state.cursor.end(&self.value),
content.clone(), content.clone(),
); );

View File

@ -1,7 +1,8 @@
//! Track the cursor of a text input.
use crate::widget::text_input::Value; use crate::widget::text_input::Value;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
enum State { pub enum State {
Index(usize), Index(usize),
Selection { start: usize, end: usize }, Selection { start: usize, end: usize },
} }
@ -20,7 +21,6 @@ impl Default for Cursor {
} }
impl Cursor { impl Cursor {
/* index move methods */
pub fn move_to(&mut self, position: usize) { pub fn move_to(&mut self, position: usize) {
self.state = State::Index(position); self.state = State::Index(position);
} }
@ -30,42 +30,40 @@ impl Cursor {
} }
pub fn move_right_by_words(&mut self, value: &Value) { pub fn move_right_by_words(&mut self, value: &Value) {
self.move_to(value.next_end_of_word(self.right())) self.move_to(value.next_end_of_word(self.right(value)))
} }
pub fn move_right_by_amount(&mut self, value: &Value, amount: usize) { pub fn move_right_by_amount(&mut self, value: &Value, amount: usize) {
match self.state { match self.state(value) {
State::Index(index) => { State::Index(index) => {
self.move_to(index.saturating_add(amount).min(value.len())) self.move_to(index.saturating_add(amount).min(value.len()))
} }
State::Selection { .. } => self.move_to(self.right()), State::Selection { start, end } => self.move_to(end.max(start)),
} }
} }
pub fn move_left(&mut self) { pub fn move_left(&mut self, value: &Value) {
match self.state { match self.state(value) {
State::Index(index) if index > 0 => self.move_to(index - 1), State::Index(index) if index > 0 => self.move_to(index - 1),
State::Selection { .. } => self.move_to(self.left()), State::Selection { start, end } => self.move_to(start.min(end)),
_ => self.move_to(0), _ => self.move_to(0),
} }
} }
pub fn move_left_by_words(&mut self, value: &Value) { pub fn move_left_by_words(&mut self, value: &Value) {
self.move_to(value.previous_start_of_word(self.right())); self.move_to(value.previous_start_of_word(self.left(value)));
} }
/* end of index move methods */
/* expand/shrink selection */
pub fn select_range(&mut self, start: usize, end: usize) { pub fn select_range(&mut self, start: usize, end: usize) {
if start != end { if start == end {
self.state = State::Selection { start, end };
} else {
self.state = State::Index(start); self.state = State::Index(start);
} else {
self.state = State::Selection { start, end };
} }
} }
pub fn select_left(&mut self) { pub fn select_left(&mut self, value: &Value) {
match self.state { match self.state(value) {
State::Index(index) if index > 0 => { State::Index(index) if index > 0 => {
self.select_range(index, index - 1) self.select_range(index, index - 1)
} }
@ -77,7 +75,7 @@ impl Cursor {
} }
pub fn select_right(&mut self, value: &Value) { pub fn select_right(&mut self, value: &Value) {
match self.state { match self.state(value) {
State::Index(index) if index < value.len() => { State::Index(index) if index < value.len() => {
self.select_range(index, index + 1) self.select_range(index, index + 1)
} }
@ -89,7 +87,7 @@ impl Cursor {
} }
pub fn select_left_by_words(&mut self, value: &Value) { pub fn select_left_by_words(&mut self, value: &Value) {
match self.state { match self.state(value) {
State::Index(index) => { State::Index(index) => {
self.select_range(index, value.previous_start_of_word(index)) self.select_range(index, value.previous_start_of_word(index))
} }
@ -100,7 +98,7 @@ impl Cursor {
} }
pub fn select_right_by_words(&mut self, value: &Value) { pub fn select_right_by_words(&mut self, value: &Value) {
match self.state { match self.state(value) {
State::Index(index) => { State::Index(index) => {
self.select_range(index, value.next_end_of_word(index)) self.select_range(index, value.next_end_of_word(index))
} }
@ -113,51 +111,56 @@ impl Cursor {
pub fn select_all(&mut self, value: &Value) { pub fn select_all(&mut self, value: &Value) {
self.select_range(0, value.len()); self.select_range(0, value.len());
} }
/* end of selection section */
/* helpers */ pub fn state(&self, value: &Value) -> State {
// get start position of selection (can be left OR right boundary of selection) or index
pub(crate) fn start(&self) -> usize {
match self.state { match self.state {
State::Index(index) => State::Index(index.min(value.len())),
State::Selection { start, end } => {
let start = start.min(value.len());
let end = end.min(value.len());
if start == end {
State::Index(start)
} else {
State::Selection { start, end }
}
}
}
}
pub fn start(&self, value: &Value) -> usize {
let start = match self.state {
State::Index(index) => index, State::Index(index) => index,
State::Selection { start, .. } => start, State::Selection { start, .. } => start,
} };
start.min(value.len())
} }
// get end position of selection (can be left OR right boundary of selection) or index pub fn end(&self, value: &Value) -> usize {
pub fn end(&self) -> usize { let end = match self.state {
match self.state {
State::Index(index) => index, State::Index(index) => index,
State::Selection { end, .. } => end, State::Selection { end, .. } => end,
} };
end.min(value.len())
} }
// get left boundary of selection or index fn left(&self, value: &Value) -> usize {
pub fn left(&self) -> usize { match self.state(value) {
match self.state {
State::Index(index) => index, State::Index(index) => index,
State::Selection { start, end } => start.min(end), State::Selection { start, end } => start.min(end),
} }
} }
// get right boundary of selection or index fn right(&self, value: &Value) -> usize {
pub fn right(&self) -> usize { match self.state(value) {
match self.state {
State::Index(index) => index, State::Index(index) => index,
State::Selection { start, end } => start.max(end), State::Selection { start, end } => start.max(end),
} }
} }
pub fn cursor_position(&self, value: &Value) -> usize { pub fn selection(&self) -> Option<(usize, usize)> {
match self.state {
State::Index(index) => index.min(value.len()),
State::Selection { end, .. } => end.min(value.len()),
}
}
// returns Option of left and right border of selection
// a second method that returns start and end may be useful (see below)
pub fn selection_position(&self) -> Option<(usize, usize)> {
match self.state { match self.state {
State::Selection { start, end } => { State::Selection { start, end } => {
Some((start.min(end), start.max(end))) Some((start.min(end), start.max(end)))
@ -165,11 +168,4 @@ impl Cursor {
_ => None, _ => None,
} }
} }
/* pub fn selection_position(&self) -> Option<(usize, usize)> {
match self.state {
State::Selection { start, end } => Some((start, end)),
_ => None,
}
} */
} }

View File

@ -1,8 +1,9 @@
use crate::{text_input::StyleSheet, Primitive, Renderer}; use crate::{text_input::StyleSheet, Primitive, Renderer};
use iced_native::{ use iced_native::{
text_input, Background, Color, Font, HorizontalAlignment, MouseCursor, text_input::{self, cursor},
Point, Rectangle, Size, Vector, VerticalAlignment, Background, Color, Font, HorizontalAlignment, MouseCursor, Point,
Rectangle, Size, Vector, VerticalAlignment,
}; };
use std::f32; use std::f32;
@ -41,12 +42,19 @@ impl text_input::Renderer for Renderer {
font: Font, font: Font,
) -> f32 { ) -> f32 {
if state.is_focused() { if state.is_focused() {
let cursor = state.cursor();
let focus_position = match cursor.state(value) {
cursor::State::Index(i) => i,
cursor::State::Selection { end, .. } => end,
};
let (_, offset) = measure_cursor_and_scroll_offset( let (_, offset) = measure_cursor_and_scroll_offset(
self, self,
text_bounds, text_bounds,
value, value,
size, size,
state.cursor().cursor_position(value), focus_position,
font, font,
); );
@ -111,70 +119,91 @@ impl text_input::Renderer for Renderer {
}; };
let (contents_primitive, offset) = if state.is_focused() { let (contents_primitive, offset) = if state.is_focused() {
let (text_value_width, offset) = measure_cursor_and_scroll_offset( let cursor = state.cursor();
self,
text_bounds,
value,
size,
state.cursor().cursor_position(value),
font,
);
let selection = match state.cursor().selection_position() { let (cursor_primitive, offset) = match cursor.state(value) {
None => Primitive::None, cursor::State::Index(position) => {
Some(_) => { let (text_value_width, offset) =
let (cursor_left_offset, _) =
measure_cursor_and_scroll_offset( measure_cursor_and_scroll_offset(
self, self,
text_bounds, text_bounds,
value, value,
size, size,
state.cursor().left(), position,
font, font,
); );
let (cursor_right_offset, _) =
measure_cursor_and_scroll_offset( (
self, Primitive::Quad {
text_bounds, bounds: Rectangle {
value, x: text_bounds.x + text_value_width,
size, y: text_bounds.y,
state.cursor().right(), width: 1.0,
font, height: text_bounds.height,
); },
let width = cursor_right_offset - cursor_left_offset; background: Background::Color(
Primitive::Quad { style_sheet.value_color(),
bounds: Rectangle { ),
x: text_bounds.x + cursor_left_offset, border_radius: 0,
y: text_bounds.y, border_width: 0,
width, border_color: Color::TRANSPARENT,
height: text_bounds.height,
}, },
background: Background::Color( offset,
style_sheet.selection_color(), )
),
border_radius: 0,
border_width: 0,
border_color: Color::TRANSPARENT,
}
} }
}; cursor::State::Selection { start, end } => {
let left = start.min(end);
let right = end.max(start);
let cursor = Primitive::Quad { let (left_position, left_offset) =
bounds: Rectangle { measure_cursor_and_scroll_offset(
x: text_bounds.x + text_value_width, self,
y: text_bounds.y, text_bounds,
width: 1.0, value,
height: text_bounds.height, size,
}, left,
background: Background::Color(style_sheet.value_color()), font,
border_radius: 0, );
border_width: 0,
border_color: Color::TRANSPARENT, let (right_position, right_offset) =
measure_cursor_and_scroll_offset(
self,
text_bounds,
value,
size,
right,
font,
);
let width = right_position - left_position;
(
Primitive::Quad {
bounds: Rectangle {
x: text_bounds.x + left_position,
y: text_bounds.y,
width,
height: text_bounds.height,
},
background: Background::Color(
style_sheet.selection_color(),
),
border_radius: 0,
border_width: 0,
border_color: Color::TRANSPARENT,
},
if end == right {
right_offset
} else {
left_offset
},
)
}
}; };
( (
Primitive::Group { Primitive::Group {
primitives: vec![selection, text_value, cursor], primitives: vec![cursor_primitive, text_value],
}, },
Vector::new(offset as u32, 0), Vector::new(offset as u32, 0),
) )