From 14fb7e13fb272a43b9924d5d2823b0c105d781b0 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector0193@gmail.com>
Date: Thu, 5 Dec 2019 03:09:39 +0100
Subject: Place `TextInput` cursor position on click

---
 native/src/renderer/null.rs            |  4 ++
 native/src/widget/text_input.rs        | 78 ++++++++++++++++++++++++++++++++--
 wgpu/src/renderer/widget/text_input.rs | 37 +++++++++-------
 3 files changed, 100 insertions(+), 19 deletions(-)

diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index da0e5159..02f033a5 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -89,6 +89,10 @@ impl text_input::Renderer for Null {
         20
     }
 
+    fn measure_value(&self, _value: &str, _size: u16) -> f32 {
+        0.0
+    }
+
     fn draw(
         &mut self,
         _bounds: Rectangle,
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index f97ed424..764f87cf 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -160,7 +160,7 @@ where
         layout: Layout<'_>,
         cursor_position: Point,
         messages: &mut Vec<Message>,
-        _renderer: &Renderer,
+        renderer: &Renderer,
     ) {
         match event {
             Event::Mouse(mouse::Event::Input {
@@ -170,8 +170,22 @@ where
                 self.state.is_focused =
                     layout.bounds().contains(cursor_position);
 
-                if self.state.cursor_position(&self.value) == 0 {
-                    self.state.move_cursor_to_end(&self.value);
+                if self.state.is_focused {
+                    let text_layout = layout.children().next().unwrap();
+                    let target = cursor_position.x - text_layout.bounds().x;
+
+                    if target < 0.0 {
+                        self.state.cursor_position = 0;
+                    } else {
+                        self.state.cursor_position = find_cursor_position(
+                            renderer,
+                            target,
+                            &self.value,
+                            self.size.unwrap_or(renderer.default_size()),
+                            0,
+                            self.value.len(),
+                        );
+                    }
                 }
             }
             Event::Keyboard(keyboard::Event::CharacterReceived(c))
@@ -275,6 +289,11 @@ pub trait Renderer: crate::Renderer + Sized {
     /// [`TextInput`]: struct.TextInput.html
     fn default_size(&self) -> u16;
 
+    /// Returns the width of the value of the [`TextInput`].
+    ///
+    /// [`TextInput`]: struct.TextInput.html
+    fn measure_value(&self, value: &str, size: u16) -> f32;
+
     /// Draws a [`TextInput`].
     ///
     /// It receives:
@@ -437,3 +456,56 @@ impl Value {
         let _ = self.0.remove(index);
     }
 }
+
+// TODO: Reduce allocations
+fn find_cursor_position<Renderer: self::Renderer>(
+    renderer: &Renderer,
+    target: f32,
+    value: &Value,
+    size: u16,
+    start: usize,
+    end: usize,
+) -> usize {
+    if start >= end {
+        if start == 0 {
+            return 0;
+        }
+
+        let prev = value.until(start - 1);
+        let next = value.until(start);
+
+        let prev_width = renderer.measure_value(&prev.to_string(), size);
+        let next_width = renderer.measure_value(&next.to_string(), size);
+
+        if (target - next_width).abs() > (target - prev_width).abs() {
+            return start - 1;
+        } else {
+            return start;
+        }
+    }
+
+    let index = (end - start) / 2;
+    let subvalue = value.until(start + index);
+
+    let width = renderer.measure_value(&subvalue.to_string(), size);
+
+    if width > target {
+        find_cursor_position(
+            renderer,
+            target,
+            value,
+            size,
+            start,
+            start + index,
+        )
+    } else {
+        find_cursor_position(
+            renderer,
+            target,
+            value,
+            size,
+            start + index + 1,
+            end,
+        )
+    }
+}
diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs
index d64fca6d..c6c64b88 100644
--- a/wgpu/src/renderer/widget/text_input.rs
+++ b/wgpu/src/renderer/widget/text_input.rs
@@ -12,6 +12,24 @@ impl text_input::Renderer for Renderer {
         20
     }
 
+    fn measure_value(&self, value: &str, size: u16) -> f32 {
+        let (mut width, _) = self.text_pipeline.measure(
+            value,
+            f32::from(size),
+            Font::Default,
+            Size::INFINITY,
+        );
+
+        let spaces_at_the_end = value.len() - value.trim_end().len();
+
+        if spaces_at_the_end > 0 {
+            let space_width = self.text_pipeline.space_width(size as f32);
+            width += spaces_at_the_end as f32 * space_width;
+        }
+
+        width
+    }
+
     fn draw(
         &mut self,
         bounds: Rectangle,
@@ -48,7 +66,6 @@ impl text_input::Renderer for Renderer {
             border_radius: 4,
         };
 
-        let size = f32::from(size);
         let text = value.to_string();
 
         let text_value = Primitive::Text {
@@ -68,7 +85,7 @@ impl text_input::Renderer for Renderer {
                 width: f32::INFINITY,
                 ..text_bounds
             },
-            size,
+            size: f32::from(size),
             horizontal_alignment: HorizontalAlignment::Left,
             vertical_alignment: VerticalAlignment::Center,
         };
@@ -77,20 +94,8 @@ impl text_input::Renderer for Renderer {
             let text_before_cursor =
                 value.until(state.cursor_position(value)).to_string();
 
-            let (mut text_value_width, _) = self.text_pipeline.measure(
-                &text_before_cursor,
-                size,
-                Font::Default,
-                Size::new(f32::INFINITY, text_bounds.height),
-            );
-
-            let spaces_at_the_end =
-                text_before_cursor.len() - text_before_cursor.trim_end().len();
-
-            if spaces_at_the_end > 0 {
-                let space_width = self.text_pipeline.space_width(size);
-                text_value_width += spaces_at_the_end as f32 * space_width;
-            }
+            let text_value_width =
+                self.measure_value(&text_before_cursor, size);
 
             let cursor = Primitive::Quad {
                 bounds: Rectangle {
-- 
cgit