Преглед изворни кода

app: chatedit drag select working

darkfi пре 1 година
родитељ
комит
281b586eb4

+ 0 - 5
bin/app/src/app/node.rs

@@ -361,11 +361,6 @@ pub fn create_chatedit(name: &str) -> SceneNode {
     prop.set_array_len(2);
     node.add_property(prop).unwrap();
 
-    let mut prop = Property::new("cursor_pos", PropertyType::Uint32, PropertySubType::Pixel);
-    prop.set_ui_text("Cursor Pos", "Cursor position within the text");
-    prop.set_range_u32(0, u32::MAX);
-    node.add_property(prop).unwrap();
-
     let prop = Property::new("font_size", PropertyType::Float32, PropertySubType::Pixel);
     node.add_property(prop).unwrap();
 

+ 10 - 0
bin/app/src/app/schema/test.rs

@@ -506,6 +506,16 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     node.set_property_f32(atom, Role::App, "linespacing", 50.).unwrap();
     node.set_property_f32(atom, Role::App, "descent", 10.).unwrap();
     node.set_property_f32(atom, Role::App, "font_size", 40.).unwrap();
+
+    #[cfg(target_os = "android")]
+    {
+        let prop = node.get_property("padding").unwrap();
+        prop.clone().set_f32(atom, Role::App, 0, 80.).unwrap();
+        prop.clone().set_f32(atom, Role::App, 1, 80.).unwrap();
+
+        node.set_property_f32(atom, Role::App, "font_size", 60.).unwrap();
+    }
+
     //node.set_property_str(atom, Role::App, "text", "hello king!😁🍆jelly 🍆1234").unwrap();
     let prop = node.get_property("text_color").unwrap();
     prop.clone().set_f32(atom, Role::App, 0, 1.).unwrap();

+ 10 - 1
bin/app/src/gfx/linalg.rs

@@ -54,7 +54,7 @@ impl Div<f32> for Dimension {
     }
 }
 
-#[derive(Clone, Copy, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Copy, Default, SerialEncodable, SerialDecodable)]
 pub struct Point {
     pub x: f32,
     pub y: f32,
@@ -248,6 +248,9 @@ impl Rectangle {
     pub fn top_right(&self) -> Point {
         Point { x: self.rhs(), y: self.y }
     }
+    pub fn bot_left(&self) -> Point {
+        Point { x: self.x, y: self.y + self.h }
+    }
 
     pub fn dim(&self) -> Dimension {
         Dimension { w: self.w, h: self.h }
@@ -319,3 +322,9 @@ impl std::fmt::Debug for Rectangle {
         write!(f, "({}, {}, {}, {})", self.x, self.y, self.w, self.h)
     }
 }
+
+impl From<parley::Rect> for Rectangle {
+    fn from(rect: parley::Rect) -> Self {
+        Self::new(rect.x0 as f32, rect.y0 as f32, rect.width() as f32, rect.height() as f32)
+    }
+}

+ 16 - 16
bin/app/src/text2/editor/android.rs

@@ -138,13 +138,8 @@ impl Editor {
     }
 
     pub fn select_word_at_point(&self, pos: Point) {
-        let edit = android::get_editable(self.composer_id).unwrap();
-
         let select = parley::Selection::word_from_point(&self.layout, pos.x, pos.y).text_range();
-        let select_start = byte_to_char16_index(&edit.buffer, select.start).unwrap();
-        let select_end = byte_to_char16_index(&edit.buffer, select.end).unwrap();
-        android::set_selection(self.composer_id, select_start, select_end);
-        t!("select_word_at_point({pos:?}) -> android::set_selection({}, {select_start}, {select_end})", self.composer_id);
+        self.set_selection(select.start, select.end);
     }
 
     pub fn get_cursor_pos(&self) -> Option<Point> {
@@ -199,24 +194,29 @@ impl Editor {
         let select_end = char16_to_byte_index(&edit.buffer, edit.select_end).unwrap();
         Some(edit.buffer[select_start..select_end].to_string())
     }
-    pub fn selection(&self) -> parley::Selection {
-        let Some(edit) = android::get_editable(self.composer_id) else {
-            return Default::default()
-        };
+    pub fn selection(&self, focus_side: isize) -> parley::Selection {
+        assert!(focus_side == -1 || focus_side == 1);
+        let Some(edit) = android::get_editable(self.composer_id) else { return Default::default() };
 
         let select_start = char16_to_byte_index(&edit.buffer, edit.select_start).unwrap();
         let select_end = char16_to_byte_index(&edit.buffer, edit.select_end).unwrap();
 
-        let anchor = parley::Cursor::from_byte_index(
-            &self.layout,
-            select_start,
-            parley::Affinity::Downstream,
-        );
+        let (anchor_idx, focus_idx) =
+            if focus_side < 0 { (select_end, select_start) } else { (select_start, select_end) };
+
+        let anchor =
+            parley::Cursor::from_byte_index(&self.layout, anchor_idx, parley::Affinity::Downstream);
         let focus =
-            parley::Cursor::from_byte_index(&self.layout, select_end, parley::Affinity::Downstream);
+            parley::Cursor::from_byte_index(&self.layout, focus_idx, parley::Affinity::Downstream);
 
         parley::Selection::new(anchor, focus)
     }
+    pub fn set_selection(&self, select_start: usize, select_end: usize) {
+        let edit = android::get_editable(self.composer_id).unwrap();
+        let select_start = byte_to_char16_index(&edit.buffer, select_start).unwrap();
+        let select_end = byte_to_char16_index(&edit.buffer, select_end).unwrap();
+        android::set_selection(self.composer_id, select_start, select_end);
+    }
 
     pub fn buffer(&self) -> String {
         let edit = android::get_editable(self.composer_id).unwrap();

+ 1 - 1
bin/app/src/text2/editor/parley.rs

@@ -109,7 +109,7 @@ impl Editor {
     pub fn selected_text(&self) -> Option<String> {
         self.editor.selected_text().map(str::to_string)
     }
-    pub fn selection(&self) -> parley::Selection {
+    pub fn selection(&self, focus_side: isize) -> parley::Selection {
         *self.editor.raw_selection()
     }
 

+ 97 - 122
bin/app/src/ui/chatedit.rs

@@ -234,7 +234,6 @@ pub struct ChatEdit {
     scroll: PropertyFloat32,
     scroll_speed: PropertyFloat32,
     padding: PropertyPtr,
-    cursor_pos: PropertyUint32,
     font_size: PropertyFloat32,
     text: PropertyStr,
     text_color: PropertyColor,
@@ -302,7 +301,6 @@ impl ChatEdit {
         let scroll_speed =
             PropertyFloat32::wrap(node_ref, Role::Internal, "scroll_speed", 0).unwrap();
         let padding = node_ref.get_property("padding").unwrap();
-        let cursor_pos = PropertyUint32::wrap(node_ref, Role::Internal, "cursor_pos", 0).unwrap();
         let font_size = PropertyFloat32::wrap(node_ref, Role::Internal, "font_size", 0).unwrap();
         let text = PropertyStr::wrap(node_ref, Role::Internal, "text", 0).unwrap();
         let text_color = PropertyColor::wrap(node_ref, Role::Internal, "text_color").unwrap();
@@ -365,7 +363,6 @@ impl ChatEdit {
             scroll: scroll.clone(),
             scroll_speed,
             padding,
-            cursor_pos,
             font_size: font_size.clone(),
             text: text.clone(),
             text_color: text_color.clone(),
@@ -455,15 +452,8 @@ impl ChatEdit {
         mesh.alloc(&self.render_api).draw_untextured()
     }
 
-    /*
-    fn draw_phone_select_handle(
-        &self,
-        mesh: &mut MeshBuilder,
-        gpos: TextPos,
-        wrapped_lines: &WrappedLines,
-        side: f32,
-    ) {
-        //debug!(target: "ui::chatedit", "draw_phone_select_handle(..., {x}, {side})");
+    fn draw_phone_select_handle(&self, mesh: &mut MeshBuilder, pos: Point, side: f32) {
+        debug!(target: "ui::chatedit", "draw_phone_select_handle(..., {pos:?}, {side})");
         let baseline = self.baseline.get();
         let select_ascent = self.select_ascent.get();
         let handle_descent = self.handle_descent.get();
@@ -475,9 +465,8 @@ impl ChatEdit {
         color_trans[3] = 0.;
 
         // find start_x
-        let (glyph_rect, line_idx) = wrapped_lines.get_glyph_info(gpos);
-        let x = glyph_rect.x;
-        let y_off = line_idx as f32 * linespacing - scroll;
+        let x = pos.x;
+        let y_off = pos.y - scroll;
 
         let y = y_off + baseline + handle_descent;
         if y < 0. {
@@ -521,7 +510,6 @@ impl ChatEdit {
         let indices = vec![0, 1, 2, 0, 2, 3];
         mesh.append(verts, indices);
     }
-    */
 
     async fn change_focus(self: Arc<Self>) {
         if !self.is_active.get() {
@@ -748,19 +736,10 @@ impl ChatEdit {
         // }
     }
 
-    /*
-    /// Call this whenever the cursor pos changes to update the external property
-    fn update_cursor_pos(&self, text_wrap: &mut TextWrap, atom: &mut PropertyAtomicGuard) {
-        let cursor_off = text_wrap.editable.get_text_before().len() as u32;
-        self.cursor_pos.set(atom, cursor_off);
-    }
-    */
-
     async fn handle_touch_start(&self, mut touch_pos: Point) -> bool {
         t!("handle_touch_start({touch_pos:?})");
-        let mut touch_info = self.touch_info.lock();
 
-        if self.try_handle_drag(&mut touch_info, touch_pos) {
+        if self.try_handle_drag(touch_pos).await {
             return true
         }
 
@@ -770,70 +749,75 @@ impl ChatEdit {
             return false
         }
 
+        let mut touch_info = self.touch_info.lock();
         touch_info.start(touch_pos);
         true
     }
-    fn try_handle_drag(&self, touch_info: &mut TouchInfo, mut touch_pos: Point) -> bool {
-        /*
-                // Is the handle visible? Use y within rect before adding the scroll.
-                let relative_y = touch_pos.y - self.rect.get().y;
-                if relative_y < 0. {
-                    return false
-                }
 
-                self.abs_to_local(&mut touch_pos);
+    async fn get_select_handles(&self) -> Option<(Point, Point)> {
+        let editor = self.editor.lock().await;
+        let layout = editor.layout();
 
-                let linespacing = self.linespacing.get();
-                let baseline = self.baseline.get();
-                let select_descent = self.select_descent.get();
-                let scroll = self.scroll.get();
+        let sel = editor.selection(1);
+        if sel.is_collapsed() {
+            assert!(!self.is_phone_select.load(Ordering::Relaxed));
+            return None
+        }
+        assert!(self.is_phone_select.load(Ordering::Relaxed));
 
-                let mut text_wrap = self.text_wrap.lock();
-                let width = self.wrap_width();
-                let wrapped_lines = text_wrap.wrap(width);
-                let selections = &text_wrap.select;
+        let mut first_pos = None;
+        let mut last_pos = Default::default();
 
-                if self.is_phone_select.load(Ordering::Relaxed) && selections.len() == 1 {
-                    let select = selections.first().unwrap();
+        sel.geometry_with(layout, |rect: parley::Rect, _| {
+            let rect = Rectangle::from(rect);
 
-                    let handle_off_y = baseline + self.handle_descent.get();
+            if first_pos.is_none() {
+                first_pos = Some(rect.bot_left());
+            }
+            last_pos = rect.corner();
+        });
 
-                    // Get left handle centerpoint
-                    let (glyph_rect, line_idx) = wrapped_lines.get_glyph_info(select.start);
-                    let mut p1 = glyph_rect.pos();
-                    // We always want the handles to be aligned so ignore the glyph's y pos
-                    p1.y = line_idx as f32 * linespacing + handle_off_y;
+        let first_pos = first_pos.unwrap();
+        Some((first_pos, last_pos))
+    }
 
-                    // Get right handle centerpoint
-                    let (glyph_rect, line_idx) = wrapped_lines.get_glyph_info(select.end);
-                    let mut p2 = glyph_rect.top_right();
-                    p2.y = line_idx as f32 * linespacing + handle_off_y;
+    async fn try_handle_drag(&self, mut touch_pos: Point) -> bool {
+        let Some((mut first_pos, mut last_pos)) = self.get_select_handles().await else {
+            return false
+        };
 
-                    // Are we within range of either one?
-                    t!("handle center points = ({p1:?}, {p2:?})");
+        self.abs_to_local(&mut touch_pos);
+        t!("localize touch_pos = {touch_pos:?}");
 
-                    const TOUCH_RADIUS_SQ: f32 = 10_000.;
+        let handle_off_y = self.handle_descent.get();
+        first_pos.y += handle_off_y;
+        last_pos.y += handle_off_y;
 
-                    if p1.dist_sq(&touch_pos) <= TOUCH_RADIUS_SQ {
-                        d!("start touch: DragSelectHandle state [side=-1]");
-                        // Set touch_state status to enable begin dragging them
-                        touch_info.state = TouchStateAction::DragSelectHandle { side: -1 };
-                        return true
-                    }
-                    if p2.dist_sq(&touch_pos) <= TOUCH_RADIUS_SQ {
-                        d!("start touch: DragSelectHandle state [side=1]");
-                        // Set touch_state status to enable begin dragging them
-                        touch_info.state = TouchStateAction::DragSelectHandle { side: 1 };
-                        return true
-                    }
-                }
+        // Are we within range of either one?
+        t!("handle center points = ({first_pos:?}, {last_pos:?})");
+
+        const TOUCH_RADIUS_SQ: f32 = 10_000.;
+
+        if first_pos.dist_sq(touch_pos) <= TOUCH_RADIUS_SQ {
+            d!("start touch: DragSelectHandle state [side=-1]");
+            // Set touch_state status to enable begin dragging them
+            let mut touch_info = self.touch_info.lock();
+            touch_info.state = TouchStateAction::DragSelectHandle { side: -1 };
+            return true
+        }
+        if last_pos.dist_sq(touch_pos) <= TOUCH_RADIUS_SQ {
+            d!("start touch: DragSelectHandle state [side=1]");
+            // Set touch_state status to enable begin dragging them
+            let mut touch_info = self.touch_info.lock();
+            touch_info.state = TouchStateAction::DragSelectHandle { side: 1 };
+            return true
+        }
 
-        */
         false
     }
 
     async fn handle_touch_move(&self, mut touch_pos: Point) -> bool {
-        //t!("handle_touch_move({touch_pos:?})");
+        t!("handle_touch_move({touch_pos:?})");
         let atom = &mut PropertyAtomicGuard::new();
         // We must update with non relative touch_pos bcos when doing vertical scrolling
         // we will modify the scroll, which is used by abs_to_local(), which is used
@@ -859,51 +843,31 @@ impl ChatEdit {
                 self.touch_info.lock().state = TouchStateAction::Select;
             }
             TouchStateAction::DragSelectHandle { side } => {
+                let handle_descent = self.handle_descent.get();
+
                 self.abs_to_local(&mut touch_pos);
-                {
-                    /*
-                    let linespacing = self.linespacing.get();
-                    let baseline = self.baseline.get();
-                    let handle_descent = self.handle_descent.get();
 
-                    let mut text_wrap = self.text_wrap.lock();
-                    let width = self.wrap_width();
-                    let wrapped_lines = text_wrap.wrap(width);
-                    let rendered = text_wrap.get_render().clone();
-                    let selections = &mut text_wrap.select;
-
-                    assert!(*side == -1 || *side == 1);
-                    assert!(self.is_phone_select.load(Ordering::Relaxed));
-                    assert_eq!(selections.len(), 1);
-                    let select = selections.first_mut().unwrap();
-
-                    let mut point = touch_pos;
-
-                    // Only allow selecting text that is visible in the box
-                    // We do our calcs relative to (0, 0) so bhs = rect_h
-                    point.y -= handle_descent + 25.;
-                    let bhs = wrapped_lines.height();
-                    point.y = min_f32(point.y, bhs);
-
-                    let mut pos = wrapped_lines.point_to_pos(point);
-
-                    if *side == -1 {
-                        let select_other_pos = &mut select.end;
-                        if pos >= *select_other_pos {
-                            pos = *select_other_pos - 1;
-                        }
-                        select.start = pos;
-                    } else {
-                        let select_other_pos = &mut select.start;
-                        if pos <= *select_other_pos {
-                            pos = *select_other_pos + 1;
-                        }
-                        select.end = pos;
-                    }
+                let editor = self.editor.lock().await;
+                let sel = editor.selection(*side);
+
+                assert!(*side == -1 || *side == 1);
+                assert!(self.is_phone_select.load(Ordering::Relaxed));
+                assert!(!sel.is_collapsed());
+
+                let mut pos = touch_pos;
+                // Only allow selecting text that is visible in the box
+                // We do our calcs relative to (0, 0) so bhs = rect_h
+                pos.y -= handle_descent + 25.;
+                //let bhs = wrapped_lines.height();
+                //pos.y = min_f32(pos.y, bhs);
+
+                let layout = editor.layout();
+                let sel = sel.extend_to_point(layout, pos.x, pos.y);
+
+                let sel = sel.text_range();
+                editor.set_selection(sel.start, sel.end);
+                drop(editor);
 
-                    self.update_select_text(&mut text_wrap, atom);
-                    */
-                }
                 self.redraw().await;
             }
             TouchStateAction::ScrollVert { start_pos, scroll_start } => {
@@ -1080,6 +1044,7 @@ impl ChatEdit {
     /// Whenever the cursor property is modified this MUST be called
     /// to recalculate the scroll x property.
     fn apply_cursor_scrolling(&self, atom: &mut PropertyAtomicGuard) {
+        /*
         let rect = self.rect.get();
 
         let cursor_pos = self.cursor_pos.get() as usize;
@@ -1126,6 +1091,7 @@ impl ChatEdit {
         }
 
         self.scroll.set(atom, scroll);
+        */
     }
 
     /*
@@ -1251,19 +1217,29 @@ impl ChatEdit {
         let editor = self.editor.lock().await;
         let layout = editor.layout();
 
-        let sel = editor.selection();
+        let sel = editor.selection(1);
         let sel_color = self.hi_bg_color.get();
         if !sel.is_collapsed() {
+            let mut first_pos = None;
+            let mut last_pos = Default::default();
+
             let mut mesh = MeshBuilder::new();
             sel.geometry_with(layout, |rect: parley::Rect, _| {
-                let rect = Rectangle::new(
-                    rect.x0 as f32,
-                    rect.y0 as f32,
-                    rect.width() as f32,
-                    rect.height() as f32,
-                );
+                let rect = Rectangle::from(rect);
+
+                if first_pos.is_none() {
+                    first_pos = Some(rect.pos());
+                }
+                last_pos = rect.top_right();
+
                 mesh.draw_filled_box(&rect, sel_color);
             });
+
+            if self.is_phone_select.load(Ordering::Relaxed) {
+                self.draw_phone_select_handle(&mut mesh, first_pos.unwrap(), -1.);
+                self.draw_phone_select_handle(&mut mesh, last_pos, 1.);
+            }
+
             instrs.push(GfxDrawInstruction::Draw(mesh.alloc(&self.render_api).draw_untextured()));
         }
 
@@ -1446,7 +1422,6 @@ impl UIObject for ChatEdit {
         // Cursor and selection might be invalidated.
         async fn reset(self_: Arc<ChatEdit>) {
             let atom = &mut PropertyAtomicGuard::new();
-            self_.cursor_pos.set(atom, 0);
             //self_.select_text.set_null(Role::Internal, 0).unwrap();
             self_.scroll.set(atom, 0.);
             self_.redraw().await;