diff options
-rw-r--r-- | native/src/input/mouse.rs | 64 | ||||
-rw-r--r-- | native/src/widget/text_input.rs | 309 | ||||
-rw-r--r-- | native/src/widget/text_input/cursor.rs | 176 | ||||
-rw-r--r-- | wgpu/src/renderer/widget/text_input.rs | 50 |
4 files changed, 315 insertions, 284 deletions
diff --git a/native/src/input/mouse.rs b/native/src/input/mouse.rs index 69dc6b4c..22c81e15 100644 --- a/native/src/input/mouse.rs +++ b/native/src/input/mouse.rs @@ -2,5 +2,69 @@ mod button; mod event; +use crate::Point; pub use button::Button; pub use event::{Event, ScrollDelta}; +use std::time::{Duration, SystemTime}; + +/// enum to track the type of the last click +#[derive(Debug, Copy, Clone)] +pub enum Interaction { + /// Last Click was a single click + Click(Point), + /// Last Click was a double click + DoubleClick(Point), + /// Last Click was a triple click + TripleClick(Point), +} + +/// Compiler bully +#[derive(Debug, Copy, Clone)] +pub struct State { + last_click: Option<Interaction>, + last_click_timestamp: Option<SystemTime>, +} + +impl Default for State { + fn default() -> Self { + State { + last_click: None, + last_click_timestamp: None, + } + } +} + +impl State { + /// processes left click to check for double/triple clicks + /// return amount of repetitive mouse clicks + /// (1 -> double click, 2 -> triple click) + pub fn update(&mut self, position: Point) -> Interaction { + self.last_click_timestamp = Some(SystemTime::now()); + self.last_click = match self.last_click { + None => Some(Interaction::Click(position)), + Some(x) => match x { + Interaction::Click(p) if self.process_click(p, position) => { + Some(Interaction::DoubleClick(position)) + } + Interaction::DoubleClick(p) + if self.process_click(p, position) => + { + Some(Interaction::TripleClick(position)) + } + _ => Some(Interaction::Click(position)), + }, + }; + self.last_click.unwrap_or(Interaction::Click(position)) + } + + fn process_click(&self, old_position: Point, new_position: Point) -> bool { + old_position == new_position + && SystemTime::now() + .duration_since( + self.last_click_timestamp.unwrap_or(SystemTime::UNIX_EPOCH), + ) + .unwrap_or(Duration::from_secs(1)) + .as_millis() + <= 500 + } +} diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 59d86f63..4e7906a8 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -4,10 +4,13 @@ //! //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html +mod cursor; use crate::{ - input::{keyboard, mouse, ButtonState}, - layout, Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + input::{keyboard, mouse, mouse::Interaction, ButtonState}, + layout, + widget::text_input::cursor::Cursor, + Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, Rectangle, + Size, Widget, }; use std::u32; @@ -209,16 +212,20 @@ where let text_layout = layout.children().next().unwrap(); let target = cursor_position.x - text_layout.bounds().x; - match self.state.cursor.process_click(cursor_position) { - 1 => self.state.cursor.select_range( - self.value.previous_start_of_word( - self.state.cursor.end(), - ), - self.value - .next_end_of_word(self.state.cursor.end()), - ), - 2 => self.state.cursor.select_all(self.value.len()), - _ => { + match self.state.mouse.update(cursor_position) { + Interaction::DoubleClick(_) => { + self.state.cursor.select_range( + self.value.previous_start_of_word( + self.state.cursor.end(), + ), + self.value + .next_end_of_word(self.state.cursor.end()), + ) + } + Interaction::TripleClick(_) => { + self.state.cursor.select_all(&self.value) + } + Interaction::Click(_) => { if target > 0.0 { let value = if self.is_secure { self.value.secure() @@ -308,17 +315,14 @@ where && self.state.is_pasting.is_none() && !c.is_control() => { - if !self.state.cursor.is_selection() { - self.value.insert(self.state.cursor.end(), c); - } else { - self.value.remove_many( - self.state.cursor.left(), - self.state.cursor.right(), - ); - self.state.cursor.move_left(); - self.value.insert(self.state.cursor.end(), c); + match self.state.cursor.selection_position() { + Some((left, right)) => { + self.value.remove_many(left, right); + self.state.cursor.move_left(); + } + _ => (), } - + self.value.insert(self.state.cursor.end(), c); self.state.cursor.move_right(&self.value); let message = (self.on_change)(self.value.to_string()); @@ -335,42 +339,38 @@ where } } keyboard::KeyCode::Backspace => { - if !self.state.cursor.is_selection() { - if self.state.cursor.start() > 0 { + match self.state.cursor.selection_position() { + Some((start, end)) => { + self.value.remove_many(start, end); self.state.cursor.move_left(); - let _ = - self.value.remove(self.state.cursor.end() - 1); - let message = - (self.on_change)(self.value.to_string()); - messages.push(message); } - } else { - self.value.remove_many( - self.state.cursor.left(), - self.state.cursor.right(), - ); - self.state.cursor.move_left(); - let message = (self.on_change)(self.value.to_string()); - messages.push(message); + None => { + if self.state.cursor.start() > 0 { + self.state.cursor.move_left(); + let _ = self + .value + .remove(self.state.cursor.start() - 1); + } + } } + let message = (self.on_change)(self.value.to_string()); + messages.push(message); } keyboard::KeyCode::Delete => { - if !self.state.cursor.is_selection() { - if self.state.cursor.end() < self.value.len() { - let _ = self.value.remove(self.state.cursor.end()); - let message = - (self.on_change)(self.value.to_string()); - messages.push(message); + match self.state.cursor.selection_position() { + Some((start, end)) => { + self.value.remove_many(start, end); + self.state.cursor.move_left(); + } + None => { + if self.state.cursor.end() < self.value.len() { + let _ = + self.value.remove(self.state.cursor.end()); + } } - } else { - self.value.remove_many( - self.state.cursor.left(), - self.state.cursor.right(), - ); - self.state.cursor.move_left(); - let message = (self.on_change)(self.value.to_string()); - messages.push(message); } + let message = (self.on_change)(self.value.to_string()); + messages.push(message); } keyboard::KeyCode::Left => { if platform::is_jump_modifier_pressed(modifiers) @@ -413,12 +413,12 @@ where } }; - if self.state.cursor.is_selection() { - self.value.remove_many( - self.state.cursor.left(), - self.state.cursor.right(), - ); - self.state.cursor.move_left(); + match self.state.cursor.selection_position() { + Some((left, right)) => { + self.value.remove_many(left, right); + self.state.cursor.move_left(); + } + _ => (), } self.value.insert_many( @@ -442,7 +442,7 @@ where } keyboard::KeyCode::A => { if platform::is_copy_paste_modifier_pressed(modifiers) { - self.state.cursor.select_range(0, self.value.len()); + self.state.cursor.select_all(&self.value); } } _ => {} @@ -596,8 +596,8 @@ pub struct State { is_focused: bool, is_pressed: bool, is_pasting: Option<Value>, - /// TODO: Compiler wants documentation here - pub cursor: cursor::Cursor, + cursor: Cursor, + mouse: crate::input::mouse::State, // TODO: Add stateful horizontal scrolling offset } @@ -617,7 +617,8 @@ impl State { is_focused: true, is_pressed: false, is_pasting: None, - cursor: cursor::Cursor::default(), + cursor: Cursor::default(), + mouse: crate::input::mouse::State::default(), } } @@ -627,6 +628,11 @@ impl State { pub fn is_focused(&self) -> bool { self.is_focused } + + /// getter for cursor + pub fn cursor(&self) -> Cursor { + self.cursor + } } /// The value of a [`TextInput`]. @@ -841,182 +847,3 @@ 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 */ - } -} diff --git a/native/src/widget/text_input/cursor.rs b/native/src/widget/text_input/cursor.rs new file mode 100644 index 00000000..bbf5448b --- /dev/null +++ b/native/src/widget/text_input/cursor.rs @@ -0,0 +1,176 @@ +use crate::widget::text_input::Value; + +#[derive(Debug, Copy, Clone)] +enum State { + Index(usize), + Selection { start: usize, end: usize }, +} + +#[derive(Debug, Copy, Clone)] +pub struct Cursor { + state: State, +} + +impl Default for Cursor { + fn default() -> Self { + Cursor { + state: State::Index(0), + } + } +} + +impl Cursor { + /* index move methods */ + pub fn move_to(&mut self, position: usize) { + self.state = State::Index(position); + } + + pub fn move_right(&mut self, value: &Value) { + self.move_right_by_amount(value, 1) + } + + pub fn move_right_by_words(&mut self, value: &Value) { + self.move_to(value.next_end_of_word(self.right())) + } + + pub fn move_right_by_amount(&mut self, value: &Value, amount: usize) { + match self.state { + State::Index(index) => { + self.move_to(index.saturating_add(amount).min(value.len())) + } + State::Selection { .. } => self.move_to(self.right()), + } + } + + pub fn move_left(&mut self) { + match self.state { + State::Index(index) if index > 0 => self.move_to(index - 1), + State::Selection { .. } => self.move_to(self.left()), + _ => self.move_to(0), + } + } + + pub fn move_left_by_words(&mut self, value: &Value) { + self.move_to(value.previous_start_of_word(self.right())); + } + /* end of index move methods */ + + /* expand/shrink selection */ + // TODO: (whole section): Return State::Cursor if start == end after operation + pub fn select_range(&mut self, start: usize, end: usize) { + self.state = State::Selection { start, end }; + } + + pub fn select_left(&mut self) { + match self.state { + State::Index(index) if index > 0 => { + self.select_range(index, index - 1) + } + State::Selection { start, end } if end > 0 => { + self.select_range(start, end - 1) + } + _ => (), + } + } + + pub fn select_right(&mut self, value: &Value) { + match self.state { + State::Index(index) if index < value.len() => { + self.select_range(index, index + 1) + } + State::Selection { start, end } if end < value.len() => { + self.select_range(start, end + 1) + } + _ => (), + } + } + + pub fn select_left_by_words(&mut self, value: &Value) { + match self.state { + State::Index(index) => { + self.select_range(index, value.previous_start_of_word(index)) + } + State::Selection { start, end } => { + self.select_range(start, value.previous_start_of_word(end)) + } + } + } + + pub fn select_right_by_words(&mut self, value: &Value) { + match self.state { + State::Index(index) => { + self.select_range(index, value.next_end_of_word(index)) + } + State::Selection { start, end } => { + self.select_range(start, value.next_end_of_word(end)) + } + } + } + + pub fn select_all(&mut self, value: &Value) { + self.select_range(0, value.len()); + } + /* end of selection section */ + + /* helpers */ + // get start position of selection (can be left OR right boundary of selection) or index + pub(crate) fn start(&self) -> usize { + match self.state { + State::Index(index) => index, + State::Selection { start, .. } => start, + } + } + + // get end position of selection (can be left OR right boundary of selection) or index + pub fn end(&self) -> usize { + match self.state { + State::Index(index) => index, + State::Selection { end, .. } => end, + } + } + + // get left boundary of selection or index + pub fn left(&self) -> usize { + match self.state { + State::Index(index) => index, + State::Selection { start, end } => start.min(end), + } + } + + // get right boundary of selection or index + pub fn right(&self) -> usize { + match self.state { + State::Index(index) => index, + State::Selection { start, end } => start.max(end), + } + } + + pub fn draw_position(&self, value: &Value) -> usize { + self.cursor_position(value) + } + + pub fn cursor_position(&self, value: &Value) -> 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 return start and end may be useful (see below) + pub fn selection_position(&self) -> Option<(usize, usize)> { + match self.state { + State::Selection { start, end } => { + Some((start.min(end), start.max(end))) + } + _ => None, + } + } + + /* pub fn selection_position(&self) -> Option<(usize, usize)> { + match self.state { + State::Selection { start, end } => Some((start, end)), + _ => None, + } + } */ +} diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs index 4e0274b8..fa108d68 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/wgpu/src/renderer/widget/text_input.rs @@ -46,7 +46,7 @@ impl text_input::Renderer for Renderer { text_bounds, value, size, - state.cursor.draw_position(value), + state.cursor().cursor_position(value), font, ); @@ -116,20 +116,20 @@ impl text_input::Renderer for Renderer { text_bounds, value, size, - state.cursor.draw_position(value), + state.cursor().cursor_position(value), font, ); - /*let selection = match cursor { - text_input::Cursor::Index(_) => Primitive::None, - text_input::Cursor::Selection { .. } => { + let selection = match state.cursor().selection_position() { + None => Primitive::None, + Some(_) => { let (cursor_left_offset, _) = measure_cursor_and_scroll_offset( self, text_bounds, value, size, - state.cursor.left(), + state.cursor().left(), font, ); let (cursor_right_offset, _) = @@ -138,7 +138,7 @@ impl text_input::Renderer for Renderer { text_bounds, value, size, - state.cursor.right(), + state.cursor().right(), font, ); let width = cursor_right_offset - cursor_left_offset; @@ -157,42 +157,6 @@ impl text_input::Renderer for Renderer { 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 { |