From c6c8cabdaf03f90b1739be828cf140d8ddb92e65 Mon Sep 17 00:00:00 2001
From: FabianLars <fabianlars@fabianlars.de>
Date: Mon, 24 Feb 2020 18:03:42 +0100
Subject: moved cursor into own file moved click tracking as a new State struct
 to input::mouse made cursor field of text_input state private brought back
 cursor type(Index, Selection) representation with a state enum cleaned out
 some stuff (but not enough/all) TODO: Documentation (sigh) TODO: Editor
 struct TODO: some (hopefully) small improvements here and there

---
 native/src/input/mouse.rs              |  64 +++++++
 native/src/widget/text_input.rs        | 309 ++++++++-------------------------
 native/src/widget/text_input/cursor.rs | 176 +++++++++++++++++++
 wgpu/src/renderer/widget/text_input.rs |  50 +-----
 4 files changed, 315 insertions(+), 284 deletions(-)
 create mode 100644 native/src/widget/text_input/cursor.rs

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 {
-- 
cgit