Text Selection completely rewritten

This commit is contained in:
FabianLars 2020-02-24 04:14:32 +01:00
parent e8bf0fc099
commit 190dcef155
2 changed files with 321 additions and 307 deletions

View file

@ -10,10 +10,7 @@ use crate::{
Rectangle, Size, Widget, Rectangle, Size, Widget,
}; };
use std::{ use std::u32;
time::{Duration, SystemTime},
u32,
};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
/// A field that can be filled with text. /// A field that can be filled with text.
@ -212,82 +209,50 @@ where
let text_layout = layout.children().next().unwrap(); let text_layout = layout.children().next().unwrap();
let target = cursor_position.x - text_layout.bounds().x; let target = cursor_position.x - text_layout.bounds().x;
if cursor_position match self.state.cursor.process_click(cursor_position) {
== self 1 => self.state.cursor.select_range(
.state self.value.previous_start_of_word(
.last_position self.state.cursor.end(),
.unwrap_or(Point { x: 0.0, y: 0.0 }) ),
&& self.state.click_count < 2 self.value
&& SystemTime::now() .next_end_of_word(self.state.cursor.end()),
.duration_since( ),
self.state 2 => self.state.cursor.select_all(self.value.len()),
.last_timestamp _ => {
.unwrap_or(SystemTime::now()), if target > 0.0 {
) let value = if self.is_secure {
.unwrap_or(Duration::from_secs(1)) self.value.secure()
.as_millis() } else {
<= 500 self.value.clone()
{ };
self.state.click_count += 1;
if self.state.click_count == 1 { let size = self
let current = .size
self.state.cursor_position(&self.value); .unwrap_or(renderer.default_size());
self.state.cursor_position = Cursor::Selection { let offset = renderer.offset(
start: self text_layout.bounds(),
.value size,
.previous_start_of_word(current.left()), &value,
end: self &self.state,
.value self.font,
.next_end_of_word(current.right()), );
}
} else if self.state.click_count == 2 { self.state.cursor.move_to(
self.state.cursor_position = Cursor::Selection { find_cursor_position(
start: 0, renderer,
end: self.value.len(), target + offset,
&value,
size,
0,
self.value.len(),
self.font,
),
);
} else {
self.state.cursor.move_to(0);
} }
} }
self.state.last_timestamp =
Option::from(SystemTime::now());
} else if target > 0.0 {
let value = if self.is_secure {
self.value.secure()
} else {
self.value.clone()
};
let size = self.size.unwrap_or(renderer.default_size());
let offset = renderer.offset(
text_layout.bounds(),
size,
&value,
&self.state,
self.font,
);
self.state.cursor_position =
Cursor::Index(find_cursor_position(
renderer,
target + offset,
&value,
size,
0,
self.value.len(),
self.font,
));
self.state.click_count = 0;
self.state.last_position =
Option::from(cursor_position);
self.state.last_timestamp =
Option::from(SystemTime::now());
} else {
self.state.click_count = 0;
self.state.last_position =
Option::from(cursor_position);
self.state.cursor_position = Cursor::Index(0);
} }
} }
@ -332,13 +297,9 @@ where
self.font, self.font,
); );
let start = match self.state.cursor_position { self.state
Cursor::Index(index) => index, .cursor
Cursor::Selection { start, .. } => start, .select_range(self.state.cursor.start(), pos);
};
self.state.cursor_position =
Cursor::Selection { start, end: pos }.simplify();
} }
} }
} }
@ -347,19 +308,18 @@ where
&& self.state.is_pasting.is_none() && self.state.is_pasting.is_none()
&& !c.is_control() => && !c.is_control() =>
{ {
let cursor = self.state.cursor_position(&self.value); if !self.state.cursor.is_selection() {
self.value.insert(self.state.cursor.end(), c);
match cursor { } else {
Cursor::Index(index) => { self.value.remove_many(
self.value.insert(index, c); self.state.cursor.left(),
} self.state.cursor.right(),
Cursor::Selection { .. } => { );
self.state.move_cursor_left(&self.value); self.state.cursor.move_left();
self.value.remove_many(cursor.left(), cursor.right()); self.value.insert(self.state.cursor.end(), c);
self.value.insert(cursor.left(), c);
}
} }
self.state.move_cursor_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());
messages.push(message); messages.push(message);
@ -375,71 +335,66 @@ where
} }
} }
keyboard::KeyCode::Backspace => { keyboard::KeyCode::Backspace => {
let cursor = self.state.cursor_position(&self.value); if !self.state.cursor.is_selection() {
if self.state.cursor.start() > 0 {
match cursor { self.state.cursor.move_left();
Cursor::Index(index) if index > 0 => { let _ =
self.state.move_cursor_left(&self.value); self.value.remove(self.state.cursor.end() - 1);
let _ = self.value.remove(index - 1);
let message = let message =
(self.on_change)(self.value.to_string()); (self.on_change)(self.value.to_string());
messages.push(message); messages.push(message);
} }
Cursor::Selection { .. } => { } else {
self.state.move_cursor_left(&self.value); self.value.remove_many(
self.value self.state.cursor.left(),
.remove_many(cursor.left(), cursor.right()); self.state.cursor.right(),
let message = );
(self.on_change)(self.value.to_string()); self.state.cursor.move_left();
messages.push(message); let message = (self.on_change)(self.value.to_string());
} messages.push(message);
_ => {}
} }
} }
keyboard::KeyCode::Delete => { keyboard::KeyCode::Delete => {
let cursor = self.state.cursor_position(&self.value); if !self.state.cursor.is_selection() {
if self.state.cursor.end() < self.value.len() {
match cursor { let _ = self.value.remove(self.state.cursor.end());
Cursor::Index(index) if index < self.value.len() => {
let _ = self.value.remove(index);
let message = let message =
(self.on_change)(self.value.to_string()); (self.on_change)(self.value.to_string());
messages.push(message); messages.push(message);
} }
Cursor::Selection { .. } => { } else {
self.state.move_cursor_left(&self.value); self.value.remove_many(
self.value self.state.cursor.left(),
.remove_many(cursor.left(), cursor.right()); self.state.cursor.right(),
let message = );
(self.on_change)(self.value.to_string()); self.state.cursor.move_left();
messages.push(message); let message = (self.on_change)(self.value.to_string());
} messages.push(message);
_ => {}
} }
} }
keyboard::KeyCode::Left => { keyboard::KeyCode::Left => {
if platform::is_jump_modifier_pressed(modifiers) if platform::is_jump_modifier_pressed(modifiers)
&& !self.is_secure && !self.is_secure
{ {
self.state.move_cursor_left_by_words(&self.value); self.state.cursor.move_left_by_words(&self.value);
} else { } else {
self.state.move_cursor_left(&self.value); self.state.cursor.move_left();
} }
} }
keyboard::KeyCode::Right => { keyboard::KeyCode::Right => {
if platform::is_jump_modifier_pressed(modifiers) if platform::is_jump_modifier_pressed(modifiers)
&& !self.is_secure && !self.is_secure
{ {
self.state.move_cursor_right_by_words(&self.value); self.state.cursor.move_right_by_words(&self.value);
} else { } else {
self.state.move_cursor_right(&self.value); self.state.cursor.move_right(&self.value);
} }
} }
keyboard::KeyCode::Home => { keyboard::KeyCode::Home => {
self.state.cursor_position = Cursor::Index(0); self.state.cursor.move_to(0);
} }
keyboard::KeyCode::End => { keyboard::KeyCode::End => {
self.state.move_cursor_to_end(&self.value); self.state.cursor.move_to(self.value.len());
} }
keyboard::KeyCode::V => { keyboard::KeyCode::V => {
if platform::is_copy_paste_modifier_pressed(modifiers) { if platform::is_copy_paste_modifier_pressed(modifiers) {
@ -458,24 +413,20 @@ where
} }
}; };
let cursor_position = if self.state.cursor.is_selection() {
self.state.cursor_position(&self.value); self.value.remove_many(
self.state.cursor.left(),
self.state.cursor.right(),
);
self.state.cursor.move_left();
}
let insert_position = match cursor_position { self.value.insert_many(
Cursor::Index(index) => index, self.state.cursor.end(),
Cursor::Selection { .. } => { content.clone(),
self.state.move_cursor_left(&self.value); );
self.value.remove_many(
cursor_position.left(),
cursor_position.right(),
);
cursor_position.left()
}
};
self.value
.insert_many(insert_position, content.clone());
self.state.move_cursor_right_by_amount( self.state.cursor.move_right_by_amount(
&self.value, &self.value,
content.len(), content.len(),
); );
@ -491,12 +442,7 @@ where
} }
keyboard::KeyCode::A => { keyboard::KeyCode::A => {
if platform::is_copy_paste_modifier_pressed(modifiers) { if platform::is_copy_paste_modifier_pressed(modifiers) {
self.state.cursor_position = { self.state.cursor.select_range(0, self.value.len());
Cursor::Selection {
start: 0,
end: self.value.len(),
}
}
} }
} }
_ => {} _ => {}
@ -650,77 +596,11 @@ pub struct State {
is_focused: bool, is_focused: bool,
is_pressed: bool, is_pressed: bool,
is_pasting: Option<Value>, is_pasting: Option<Value>,
cursor_position: Cursor, /// TODO: Compiler wants documentation here
/// Double- / Tripleclick pub cursor: cursor::Cursor,
click_count: usize,
last_position: Option<Point>,
last_timestamp: Option<std::time::SystemTime>,
// TODO: Add stateful horizontal scrolling offset // TODO: Add stateful horizontal scrolling offset
} }
/// The cursor position of a [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
#[derive(Debug, Clone, Copy)]
pub enum Cursor {
/// The cursor represents a position.
Index(usize),
/// The cursor represents a range.
Selection {
/// Where the selection started.
start: usize,
/// Where the selection was moved to.
end: usize,
},
}
impl Default for Cursor {
fn default() -> Self {
Cursor::Index(0)
}
}
impl Cursor {
/// Simplify representation to `Cursor::Index`
/// if `start` and `end` are the same.
pub fn simplify(&self) -> Self {
match self {
Cursor::Index(_) => *self,
Cursor::Selection { start, end } => {
if start == end {
Cursor::Index(*start)
} else {
*self
}
}
}
}
/// Position at which the cursor should be drawn.
pub fn position(&self) -> usize {
match *self {
Cursor::Index(index) => index,
Cursor::Selection { end, .. } => end,
}
}
/// The cursor index or left end of the selection.
pub fn left(&self) -> usize {
match *self {
Cursor::Index(index) => index,
Cursor::Selection { start, end } => start.min(end),
}
}
/// The cursor index or right end of the selection.
pub fn right(&self) -> usize {
match *self {
Cursor::Index(index) => index,
Cursor::Selection { start, end } => start.max(end),
}
}
}
impl State { impl State {
/// Creates a new [`State`], representing an unfocused [`TextInput`]. /// Creates a new [`State`], representing an unfocused [`TextInput`].
/// ///
@ -733,16 +613,11 @@ impl State {
/// ///
/// [`State`]: struct.State.html /// [`State`]: struct.State.html
pub fn focused() -> Self { pub fn focused() -> Self {
use std::usize;
Self { Self {
is_focused: true, is_focused: true,
is_pressed: false, is_pressed: false,
is_pasting: None, is_pasting: None,
cursor_position: Cursor::Index(usize::MAX), cursor: cursor::Cursor::default(),
click_count: 0,
last_position: None,
last_timestamp: None,
} }
} }
@ -752,81 +627,6 @@ impl State {
pub fn is_focused(&self) -> bool { pub fn is_focused(&self) -> bool {
self.is_focused self.is_focused
} }
/// Returns the cursor position of a [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
pub fn cursor_position(&self, value: &Value) -> Cursor {
match self.cursor_position {
Cursor::Index(index) => Cursor::Index(index.min(value.len())),
Cursor::Selection { start, end } => Cursor::Selection {
start: start.min(value.len()),
end: end.min(value.len()),
}
.simplify(),
}
}
/// Moves the cursor of a [`TextInput`] to the left.
///
/// [`TextInput`]: struct.TextInput.html
pub(crate) fn move_cursor_left(&mut self, value: &Value) {
let current = self.cursor_position(value);
self.cursor_position = match current {
Cursor::Index(index) if index > 0 => Cursor::Index(index - 1),
Cursor::Selection { .. } => Cursor::Index(current.left()),
_ => Cursor::Index(0),
}
}
/// Moves the cursor of a [`TextInput`] to the right.
///
/// [`TextInput`]: struct.TextInput.html
pub(crate) fn move_cursor_right(&mut self, value: &Value) {
self.move_cursor_right_by_amount(value, 1)
}
pub(crate) fn move_cursor_right_by_amount(
&mut self,
value: &Value,
amount: usize,
) {
let current = self.cursor_position(value);
self.cursor_position = match current {
Cursor::Index(index) => {
Cursor::Index(index.saturating_add(amount).min(value.len()))
}
Cursor::Selection { .. } => Cursor::Index(current.right()),
};
}
/// Moves the cursor of a [`TextInput`] to the previous start of a word.
///
/// [`TextInput`]: struct.TextInput.html
pub(crate) fn move_cursor_left_by_words(&mut self, value: &Value) {
let current = self.cursor_position(value);
self.cursor_position =
Cursor::Index(value.previous_start_of_word(current.left()));
}
/// Moves the cursor of a [`TextInput`] to the next end of a word.
///
/// [`TextInput`]: struct.TextInput.html
pub(crate) fn move_cursor_right_by_words(&mut self, value: &Value) {
let current = self.cursor_position(value);
self.cursor_position =
Cursor::Index(value.next_end_of_word(current.right()));
}
/// Moves the cursor of a [`TextInput`] to the end.
///
/// [`TextInput`]: struct.TextInput.html
pub(crate) fn move_cursor_to_end(&mut self, value: &Value) {
self.cursor_position = Cursor::Index(value.len());
}
} }
/// The value of a [`TextInput`]. /// The value of a [`TextInput`].
@ -1041,3 +841,182 @@ mod platform {
} }
} }
} }
mod cursor {
use crate::widget::text_input::Value;
use iced_core::Point;
use std::time::{Duration, SystemTime};
/// Even the compiler bullies me for not writing documentation
#[derive(Debug, Copy, Clone)]
pub struct Cursor {
start: usize,
end: usize,
click_count: usize,
last_click_position: Option<crate::Point>,
last_click_timestamp: Option<SystemTime>,
}
impl Default for Cursor {
fn default() -> Self {
Cursor {
start: 0,
end: 0,
click_count: 0,
last_click_position: None,
last_click_timestamp: None,
}
}
}
impl Cursor {
/* Move section */
pub fn move_to(&mut self, position: usize) {
self.start = position;
self.end = position;
}
pub fn move_right(&mut self, value: &Value) {
if self.is_selection() {
let dest = self.right();
self.start = dest;
self.end = dest;
} else if self.end < value.len() {
self.start += 1;
self.end += 1;
}
}
pub fn move_left(&mut self) {
if self.is_selection() {
let dest = self.left();
self.start = dest;
self.end = dest;
} else if self.left() > 0 {
self.start -= 1;
self.end -= 1;
}
}
pub fn move_right_by_amount(&mut self, value: &Value, amount: usize) {
self.start = self.start.saturating_add(amount).min(value.len());
self.end = self.end.saturating_add(amount).min(value.len());
}
pub fn move_left_by_words(&mut self, value: &Value) {
let (left, _) = self.cursor_position(value);
self.move_to(value.previous_start_of_word(left));
}
pub fn move_right_by_words(&mut self, value: &Value) {
let (_, right) = self.cursor_position(value);
self.move_to(value.next_end_of_word(right));
}
/* Move section end */
/* Selection section */
pub fn select_range(&mut self, start: usize, end: usize) {
self.start = start;
self.end = end;
}
pub fn select_left(&mut self) {
if self.end > 0 {
self.end -= 1;
}
}
pub fn select_right(&mut self, value: &Value) {
if self.end < value.len() {
self.end += 1;
}
}
pub fn select_left_by_words(&mut self, value: &Value) {
self.end = value.previous_start_of_word(self.start);
}
pub fn select_right_by_words(&mut self, value: &Value) {
self.end = value.next_end_of_word(self.start);
}
pub fn select_all(&mut self, len: usize) {
self.start = 0;
self.end = len;
}
/* Selection section end */
/* Double/Triple click section */
// returns the amount of clicks on the same position in specific timeframe
// (1=double click, 2=triple click)
pub fn process_click(&mut self, position: Point) -> usize {
if position
== self.last_click_position.unwrap_or(Point { x: 0.0, y: 0.0 })
&& self.click_count < 2
&& SystemTime::now()
.duration_since(
self.last_click_timestamp
.unwrap_or(SystemTime::UNIX_EPOCH),
)
.unwrap_or(Duration::from_secs(1))
.as_millis()
<= 500
{
self.click_count += 1;
} else {
self.click_count = 0;
}
self.last_click_position = Option::from(position);
self.last_click_timestamp = Option::from(SystemTime::now());
self.click_count
}
/* Double/Triple click section end */
/* "get info about cursor/selection" section */
pub fn is_selection(&self) -> bool {
self.start != self.end
}
// get start position of selection (can be left OR right boundary of selection)
pub(crate) fn start(&self) -> usize {
self.start
}
// get end position of selection (can be left OR right boundary of selection)
pub fn end(&self) -> usize {
self.end
}
// get left boundary of selection
pub fn left(&self) -> usize {
self.start.min(self.end)
}
// get right boundary of selection
pub fn right(&self) -> usize {
self.start.max(self.end)
}
pub fn cursor_position(&self, value: &Value) -> (usize, usize) {
(self.start.min(value.len()), self.end.min(value.len()))
}
pub fn cursor_position_left(&self, value: &Value) -> usize {
let (a, b) = self.cursor_position(value);
a.min(b)
}
pub fn cursor_position_right(&self, value: &Value) -> usize {
let (a, b) = self.cursor_position(value);
a.max(b)
}
pub fn draw_position(&self, value: &Value) -> usize {
let (_, end) = self.cursor_position(value);
end
}
/* "get info about cursor/selection" section end */
}
}

View file

@ -46,7 +46,7 @@ impl text_input::Renderer for Renderer {
text_bounds, text_bounds,
value, value,
size, size,
state.cursor_position(value).position(), state.cursor.draw_position(value),
font, font,
); );
@ -111,17 +111,16 @@ impl text_input::Renderer for Renderer {
}; };
let (contents_primitive, offset) = if state.is_focused() { let (contents_primitive, offset) = if state.is_focused() {
let cursor = state.cursor_position(value);
let (text_value_width, offset) = measure_cursor_and_scroll_offset( let (text_value_width, offset) = measure_cursor_and_scroll_offset(
self, self,
text_bounds, text_bounds,
value, value,
size, size,
cursor.position(), state.cursor.draw_position(value),
font, font,
); );
let selection = match cursor { /*let selection = match cursor {
text_input::Cursor::Index(_) => Primitive::None, text_input::Cursor::Index(_) => Primitive::None,
text_input::Cursor::Selection { .. } => { text_input::Cursor::Selection { .. } => {
let (cursor_left_offset, _) = let (cursor_left_offset, _) =
@ -130,7 +129,7 @@ impl text_input::Renderer for Renderer {
text_bounds, text_bounds,
value, value,
size, size,
cursor.left(), state.cursor.left(),
font, font,
); );
let (cursor_right_offset, _) = let (cursor_right_offset, _) =
@ -139,7 +138,7 @@ impl text_input::Renderer for Renderer {
text_bounds, text_bounds,
value, value,
size, size,
cursor.right(), state.cursor.right(),
font, font,
); );
let width = cursor_right_offset - cursor_left_offset; let width = cursor_right_offset - cursor_left_offset;
@ -158,6 +157,42 @@ impl text_input::Renderer for Renderer {
border_color: Color::TRANSPARENT, border_color: Color::TRANSPARENT,
} }
} }
};*/
let selection = if !state.cursor.is_selection() {
Primitive::None
} else {
let (cursor_left_offset, _) = measure_cursor_and_scroll_offset(
self,
text_bounds,
value,
size,
state.cursor.left(),
font,
);
let (cursor_right_offset, _) = measure_cursor_and_scroll_offset(
self,
text_bounds,
value,
size,
state.cursor.right(),
font,
);
let width = cursor_right_offset - cursor_left_offset;
Primitive::Quad {
bounds: Rectangle {
x: text_bounds.x + cursor_left_offset,
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,
}
}; };
let cursor = Primitive::Quad { let cursor = Primitive::Quad {