Просмотр исходного кода

app: android port new handle_select and scroll to android

darkfi 9 месяцев назад
Родитель
Сommit
e0c757546a

+ 1 - 1
bin/app/java/autosuggest/CustomInputConnection.java

@@ -32,7 +32,7 @@ import android.view.inputmethod.SurroundingText;
 import android.view.inputmethod.InputMethodManager;
 
 public class CustomInputConnection extends BaseInputConnection {
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
     private int id = -1;
 
     private View mInternalView;

+ 12 - 8
bin/app/src/text2/editor/android.rs

@@ -24,7 +24,10 @@ use crate::{
     text2::{TextContext, TEXT_CTX},
     AndroidSuggestEvent,
 };
-use std::sync::atomic::{AtomicBool, Ordering};
+use std::{
+    cmp::{max, min},
+    sync::atomic::{AtomicBool, Ordering},
+};
 
 macro_rules! t { ($($arg:tt)*) => { trace!(target: "text::editor::android", $($arg)*); } }
 
@@ -102,7 +105,7 @@ impl Editor {
         }
 
         //android::focus(self.composer_id).unwrap();
-        //let atxt = "A berry is small 😊 and pulpy.";
+        //let atxt = "A berry is small juicy 😊 pulpy and edible.";
         //let atxt = "A berry is a small, pulpy, and often edible fruit. Typically, berries are juicy, rounded, brightly colored, sweet, sour or tart, and do not have a stone or pit, although many pips or seeds may be present. Common examples of berries in the culinary sense are strawberries, raspberries, blueberries, blackberries, white currants, blackcurrants, and redcurrants. In Britain, soft fruit is a horticultural term for such fruits. The common usage of the term berry is different from the scientific or botanical definition of a berry, which refers to a fruit produced from the ovary of a single flower where the outer layer of the ovary wall develops into an edible fleshy portion (pericarp). The botanical definition includes many fruits that are not commonly known or referred to as berries, such as grapes, tomatoes, cucumbers, eggplants, bananas, and chili peppers.";
         //let atxt = "small berry terry";
         //android::set_text(self.composer_id, atxt);
@@ -194,7 +197,7 @@ impl Editor {
         android::set_selection(self.composer_id, pos, pos);
     }
 
-    pub fn select_word_at_point(&self, pos: Point) {
+    pub fn select_word_at_point(&mut self, pos: Point) {
         let select = parley::Selection::word_from_point(&self.layout, pos.x, pos.y);
         assert!(!select.is_collapsed());
         let select = select.text_range();
@@ -251,9 +254,10 @@ impl Editor {
         if edit.select_start == edit.select_end {
             return None
         }
-        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();
-        Some(edit.buffer[select_start..select_end].to_string())
+        let anchor = char16_to_byte_index(&edit.buffer, edit.select_start).unwrap();
+        let index = char16_to_byte_index(&edit.buffer, edit.select_end).unwrap();
+        let (start, end) = (min(anchor, index), max(anchor, index));
+        Some(edit.buffer[start..end].to_string())
     }
     pub fn selection(&self) -> parley::Selection {
         let edit = android::get_editable(self.composer_id).unwrap();
@@ -272,8 +276,8 @@ impl Editor {
 
         parley::Selection::new(anchor, focus)
     }
-    pub fn set_selection(&self, select_start: usize, select_end: usize) {
-        t!("set_selection({select_start}, {select_end})");
+    pub fn set_selection(&mut self, select_start: usize, select_end: usize) {
+        //t!("set_selection({select_start}, {select_end})");
         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();

+ 14 - 3
bin/app/src/text2/editor/parley.rs

@@ -95,7 +95,7 @@ impl Editor {
     pub fn move_to_pos(&self, _pos: Point) {
         unimplemented!()
     }
-    pub fn select_word_at_point(&self, _pos: Point) {
+    pub fn select_word_at_point(&mut self, _pos: Point) {
         unimplemented!()
     }
 
@@ -137,8 +137,19 @@ impl Editor {
     pub fn selection(&self) -> parley::Selection {
         *self.editor.raw_selection()
     }
-    pub fn set_selection(&self, _select_start: usize, _select_end: usize) {
-        unimplemented!()
+    /// Android uses byte indexes whereas parley has its own things. So this API is a compromise
+    /// between them both.
+    pub fn set_selection(&mut self, select_start: usize, select_end: usize) {
+        let anchor = parley::Cursor::from_byte_index(
+            &self.layout,
+            select_start,
+            parley::Affinity::Downstream,
+        );
+        let focus =
+            parley::Cursor::from_byte_index(&self.layout, select_end, parley::Affinity::Downstream);
+
+        let sel = parley::Selection::new(anchor, focus);
+        self.editor.set_selection(sel);
     }
 
     #[allow(dead_code)]

+ 75 - 80
bin/app/src/ui/edit/mod.rs

@@ -76,7 +76,7 @@ const SELECT_SCROLL_TRAVEL_SPEED: f32 = 1.;
 macro_rules! d { ($($arg:tt)*) => { debug!(target: "ui::edit", $($arg)*); } }
 macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::edit", $($arg)*); } }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 enum TouchStateAction {
     Inactive,
     Started { pos: Point, instant: std::time::Instant },
@@ -247,7 +247,7 @@ pub struct BaseEdit {
     /// Used to explicitly hide the cursor. Must be manually re-enabled.
     hide_cursor: AtomicBool,
     /// Used to start select and scroll when mouse moves outside widget rect.
-    sel_sender: SyncMutex<Option<async_channel::Sender<Option<Point>>>>,
+    sel_sender: SyncMutex<Option<async_channel::Sender<Option<(Point, Option<isize>)>>>>,
     scroll: Arc<AtomicF32>,
 
     touch_info: SyncMutex<TouchInfo>,
@@ -697,6 +697,7 @@ impl BaseEdit {
         drop(editor);
 
         // if start != end {
+        t!("is_phone_select = true");
         self.is_phone_select.store(true, Ordering::Relaxed);
         self.hide_cursor.store(true, Ordering::Relaxed);
         // }
@@ -720,8 +721,7 @@ impl BaseEdit {
         true
     }
 
-    async fn get_select_handles(&self) -> Option<(Point, Point)> {
-        let editor = self.lock_editor().await;
+    async fn get_select_handles(&self, editor: &Editor) -> Option<(Point, Point)> {
         let layout = editor.layout();
 
         let sel = editor.selection();
@@ -736,7 +736,10 @@ impl BaseEdit {
     }
 
     async fn try_handle_drag(&self, mut touch_pos: Point) -> bool {
-        let Some((mut first, mut last)) = self.get_select_handles().await else { return false };
+        let editor = self.lock_editor().await;
+        let Some((mut first, mut last)) = self.get_select_handles(&editor).await else {
+            return false
+        };
 
         self.abs_to_local(&mut touch_pos);
         t!("localize touch_pos = {touch_pos:?}");
@@ -790,7 +793,6 @@ impl BaseEdit {
             return false
         }
 
-        //t!("handle_touch_move({touch_pos:?})");
         // 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
         // to then calculate the max scroll. So it ends up jumping around.
@@ -800,6 +802,7 @@ impl BaseEdit {
             touch_info.update(&touch_pos);
             touch_info.state.clone()
         };
+        t!("handle_touch_move({touch_pos:?})  touch_state={touch_state:?}");
         match &touch_state {
             TouchStateAction::Inactive => return false,
             TouchStateAction::StartSelect => {
@@ -808,7 +811,8 @@ impl BaseEdit {
                 } else {
                     self.abs_to_local(&mut touch_pos);
 
-                    let atom = &mut self.render_api.make_guard(gfxtag!("BaseEdit::handle_touch_move"));
+                    let atom =
+                        &mut self.render_api.make_guard(gfxtag!("BaseEdit::handle_touch_move"));
                     self.start_touch_select(touch_pos, atom).await;
                     self.redraw_select(atom.batch_id).await;
                 }
@@ -828,58 +832,13 @@ impl BaseEdit {
                 // If so we gotta scroll it while selecting.
                 if !is_touch_hover {
                     // This process will begin selecting text and applying scroll too.
-                    sel_sender.send(Some(touch_pos)).await.unwrap();
+                    sel_sender.send(Some((touch_pos, Some(*side)))).await.unwrap();
                 } else {
                     // Stop any existing select/scroll process
                     sel_sender.send(None).await.unwrap();
                     // Mouse is inside so just select the text once and be done.
-                    self.handle_select(touch_pos).await;
+                    self.handle_select(touch_pos, Some(*side)).await;
                 }
-
-                // Old code
-
-                /*
-                self.abs_to_local(&mut touch_pos);
-
-                let editor = self.lock_editor().await;
-                let sel = editor.selection();
-
-                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 mut select = sel.text_range();
-
-                let layout = editor.layout();
-                t!("select  (pre): {:?}", sel.text_range());
-                let cursor = parley::Cursor::from_point(layout, pos.x, pos.y).index();
-                t!("cursor: {cursor}");
-
-                // The selection is NOT allowed to cross over itself.
-                if *side == -1 {
-                    let max_start = sel.focus().previous_visual(layout).index();
-                    t!("side == -1  max={max_start}");
-                    select.start = std::cmp::min(cursor, max_start);
-                } else {
-                    assert_eq!(*side, 1);
-                    let min_end = sel.anchor().next_visual(layout).index();
-                    t!("side == +1  min={min_end}");
-                    select.end = std::cmp::max(cursor, min_end);
-                };
-                t!("set_select({select:?})");
-
-                editor.set_selection(select.start, select.end);
-                drop(editor);
-
-                self.redraw_select(atom.batch_id).await;
-                */
             }
             TouchStateAction::ScrollVert { start_pos, scroll_start } => {
                 let travel_dist = self.behave.scroll_ctrl().travel(*start_pos, touch_pos);
@@ -936,13 +895,13 @@ impl BaseEdit {
     }
 
     fn finish_select(&self, atom: &mut PropertyAtomicGuard) {
-        t!("finish_select()");
-        self.is_phone_select.store(false, Ordering::Relaxed);
-        self.hide_cursor.store(false, Ordering::Relaxed);
+        self.is_phone_select.store(false, Ordering::Release);
+        self.hide_cursor.store(false, Ordering::Release);
         self.select_text.clone().set_null(atom, Role::Internal, 0).unwrap();
     }
 
-    async fn handle_select(&self, mouse_pos: Point) {
+    async fn handle_select(&self, mouse_pos: Point, side: Option<isize>) {
+        //t!("handle_select({mouse_pos:?}, {side:?})");
         let rect = self.rect.get();
         let is_mouse_hover = rect.contains(mouse_pos);
 
@@ -968,14 +927,51 @@ impl BaseEdit {
         // Move mouse pos within this widget
         self.abs_to_local(&mut clip_mouse_pos);
 
-        let seltext = {
-            let mut txt_ctx = text2::TEXT_CTX.get().await;
+        let (seltext, sel_start, sel_end) = {
             let mut editor = self.lock_editor().await;
-            let mut drv = editor.driver(&mut txt_ctx).unwrap();
-            drv.extend_selection_to_point(clip_mouse_pos.x, clip_mouse_pos.y);
-            editor.selected_text()
+
+            // The below lines rely on parley driver which android does not use
+            //let mut txt_ctx = text2::TEXT_CTX.get().await;
+            //let mut drv = editor.driver(&mut txt_ctx).unwrap();
+            //drv.extend_selection_to_point(clip_mouse_pos.x, clip_mouse_pos.y);
+            let layout = editor.layout();
+            let sel =
+                editor.selection().extend_to_point(layout, clip_mouse_pos.x, clip_mouse_pos.y);
+            let (mut start, mut end) = (sel.anchor().index(), sel.focus().index());
+            //t!("handle_select() setting ({start}, {end})");
+
+            // When dragging phone handles we prevent the selection crossing over itself.
+            // Mouse select does not have this limitation.
+            if let Some(side) = side {
+                assert!(side.abs() == 1);
+                assert!(self.is_phone_select.load(Ordering::Relaxed));
+
+                // Prevent selection crossing itself
+                if side == -1 {
+                    let max_start = sel.focus().previous_visual(layout).index();
+                    start = std::cmp::min(start, max_start);
+                    //t!("handle_select(): LHS set start={start} before {max_start}");
+                } else {
+                    let min_end = sel.anchor().next_visual(layout).index();
+                    end = std::cmp::max(end, min_end);
+                    //t!("handle_select(): RHS set end={end} after {min_end}");
+                }
+            }
+
+            editor.set_selection(start, end);
+            editor.refresh().await;
+
+            (editor.selected_text(), start, end)
         };
-        d!("Select {seltext:?} from {clip_mouse_pos:?} (unclipped: {mouse_pos:?})");
+        //d!("Select {seltext:?} from {clip_mouse_pos:?} (unclipped: {mouse_pos:?}) to ({sel_start}, {sel_end})");
+        // Android editor impl detail: selection disappears when anchor == index
+        #[cfg(target_os = "android")]
+        {
+            assert!(sel_start != sel_end);
+            //if sel_start == sel_end {
+            //    self.finish_select(atom);
+            //}
+        }
 
         // Will be None when drag select just started
         if let Some(seltext) = seltext {
@@ -1122,14 +1118,13 @@ impl BaseEdit {
     }
 
     async fn regen_phone_select_handle_mesh(&self) -> Vec<DrawInstruction> {
-        if !self.is_phone_select.load(Ordering::Relaxed) {
+        if !self.is_phone_select.load(Ordering::Acquire) {
             //t!("regen_phone_select_handle_mesh() [DISABLED]");
             return vec![]
         }
-        //t!("regen_phone_select_handle_mesh()");
-        let (first, last) = self.get_select_handles().await.unwrap();
-
         let editor = self.lock_editor().await;
+        let (first, last) = self.get_select_handles(&editor).await.unwrap();
+
         let sel = editor.selection();
         assert!(!sel.is_collapsed());
 
@@ -1281,10 +1276,10 @@ impl BaseEdit {
 
     #[cfg(target_os = "android")]
     async fn handle_android_event(&self, ev: AndroidSuggestEvent) {
-        t!("handle_android_event({ev:?})");
         if !self.is_active.get() {
             return
         }
+        t!("handle_android_event({ev:?})");
 
         let atom = &mut self.render_api.make_guard(gfxtag!("BaseEdit::handle_android_event"));
         match ev {
@@ -1308,9 +1303,6 @@ impl BaseEdit {
             AndroidSuggestEvent::Compose { .. } |
             AndroidSuggestEvent::DeleteSurroundingText { .. } => {
                 // Any editing will collapse selections
-                self.is_phone_select.store(false, Ordering::Relaxed);
-                self.hide_cursor.store(false, Ordering::Relaxed);
-
                 self.finish_select(atom);
 
                 let mut editor = self.lock_editor().await;
@@ -1474,24 +1466,27 @@ impl UIObject for BaseEdit {
                         rcv = sel_recvr.recv().fuse() => {
                             scroll_stat = rcv.unwrap();
 
-                            if let Some(mouse_pos) = scroll_stat {
+                            if let Some((mouse_pos, side)) = scroll_stat {
                                 let self_ = me2.upgrade().unwrap();
-                                self_.handle_select(mouse_pos).await;
+                                t!("select task interrupt: {mouse_pos:?} (side={side:?})");
+                                self_.handle_select(mouse_pos, side).await;
                             };
                         }
                         _ = msleep(SELECT_TASK_UPDATE_TIME).fuse() => {
-                            if let Some(mouse_pos) = scroll_stat {
+                            if let Some((mouse_pos, side)) = scroll_stat {
                                 let self_ = me2.upgrade().unwrap();
-                                self_.handle_select(mouse_pos).await;
+                                t!("select task update: {mouse_pos:?} (side={side:?})");
+                                self_.handle_select(mouse_pos, side).await;
                             };
                         }
                     }
                 } else {
                     scroll_stat = sel_recvr.recv().await.unwrap();
 
-                    if let Some(mouse_pos) = scroll_stat {
+                    if let Some((mouse_pos, side)) = scroll_stat {
                         let self_ = me2.upgrade().unwrap();
-                        self_.handle_select(mouse_pos).await;
+                        t!("select task wake up: {mouse_pos:?} (side={side:?})");
+                        self_.handle_select(mouse_pos, side).await;
                     };
                 }
             }
@@ -1706,12 +1701,12 @@ impl UIObject for BaseEdit {
         // If so we gotta scroll it while selecting.
         if !is_mouse_hover {
             // This process will begin selecting text and applying scroll too.
-            sel_sender.send(Some(mouse_pos)).await.unwrap();
+            sel_sender.send(Some((mouse_pos, None))).await.unwrap();
         } else {
             // Stop any existing select/scroll process
             sel_sender.send(None).await.unwrap();
             // Mouse is inside so just select the text once and be done.
-            self.handle_select(mouse_pos).await;
+            self.handle_select(mouse_pos, None).await;
         }
 
         true