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

app: improve scrolling, add autoscroll and fix text selection

darkfi 1 год назад
Родитель
Сommit
6441970c7a

+ 3 - 3
bin/app/src/app/schema/chat.rs

@@ -59,7 +59,7 @@ mod android_ui_consts {
     pub const BACKARROW_SCALE: f32 = 30.;
     pub const BACKARROW_SCALE: f32 = 30.;
     pub const BACKARROW_X: f32 = 50.;
     pub const BACKARROW_X: f32 = 50.;
     pub const BACKARROW_Y: f32 = 70.;
     pub const BACKARROW_Y: f32 = 70.;
-    pub const CHATEDIT_MIN_HEIGHT: f32 = 140.;
+    pub const CHATEDIT_MIN_HEIGHT: f32 = 160.;
     pub const CHATEDIT_MAX_HEIGHT: f32 = 500.;
     pub const CHATEDIT_MAX_HEIGHT: f32 = 500.;
     pub const CHATEDIT_HEIGHT: f32 = 140.;
     pub const CHATEDIT_HEIGHT: f32 = 140.;
     pub const CHATEDIT_SINGLE_LINE_Y: f32 = 120.;
     pub const CHATEDIT_SINGLE_LINE_Y: f32 = 120.;
@@ -720,7 +720,7 @@ pub async fn make(
     editbox_bg_rect_prop.add_depend(&prop, 3, "editz_h");
     editbox_bg_rect_prop.add_depend(&prop, 3, "editz_h");
 
 
     let prop = node.get_property("padding").unwrap();
     let prop = node.get_property("padding").unwrap();
-    prop.clone().set_f32(atom, Role::App, 0, TEXTBAR_BASELINE / 2.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 0, TEXTBAR_BASELINE * 0.4).unwrap();
     prop.clone().set_f32(atom, Role::App, 1, TEXTBAR_BASELINE / 2.).unwrap();
     prop.clone().set_f32(atom, Role::App, 1, TEXTBAR_BASELINE / 2.).unwrap();
 
 
     node.set_property_f32(atom, Role::App, "baseline", TEXTBAR_BASELINE).unwrap();
     node.set_property_f32(atom, Role::App, "baseline", TEXTBAR_BASELINE).unwrap();
@@ -1346,7 +1346,7 @@ pub async fn make(
     prop.clone().set_f32(atom, Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
     prop.clone().set_f32(atom, Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
     prop.clone().set_f32(atom, Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
     prop.clone().set_f32(atom, Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
     node.set_property_f32(atom, Role::App, "baseline", 0.).unwrap();
     node.set_property_f32(atom, Role::App, "baseline", 0.).unwrap();
-    node.set_property_f32(atom, Role::App, "font_size", FONTSIZE * 1.2).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", FONTSIZE * 1.24).unwrap();
     node.set_property_str(atom, Role::App, "text", "copy   paste   select all").unwrap();
     node.set_property_str(atom, Role::App, "text", "copy   paste   select all").unwrap();
     //node.set_property_bool(atom, Role::App, "debug", true).unwrap();
     //node.set_property_bool(atom, Role::App, "debug", true).unwrap();
     //node.set_property_str(atom, Role::App, "text", "anon1").unwrap();
     //node.set_property_str(atom, Role::App, "text", "anon1").unwrap();

+ 2 - 0
bin/app/src/app/schema/mod.rs

@@ -200,6 +200,7 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     let node = node.setup(|me| Shortcut::new(me)).await;
     let node = node.setup(|me| Shortcut::new(me)).await;
     window.clone().link(node);
     window.clone().link(node);
 
 
+    /*
     let node = create_gesture("zoom_gesture");
     let node = create_gesture("zoom_gesture");
     node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
     node.set_property_u32(atom, Role::App, "priority", 10).unwrap();
     let (slot, recvr) = Slot::new("zoom_gesture");
     let (slot, recvr) = Slot::new("zoom_gesture");
@@ -226,6 +227,7 @@ pub async fn make(app: &App, window: SceneNodePtr) {
     app.tasks.lock().unwrap().push(listen_zoom);
     app.tasks.lock().unwrap().push(listen_zoom);
     let node = node.setup(|me| Gesture::new(me)).await;
     let node = node.setup(|me| Gesture::new(me)).await;
     window.clone().link(node);
     window.clone().link(node);
+    */
 
 
     if COLOR_SCHEME == ColorScheme::DarkMode {
     if COLOR_SCHEME == ColorScheme::DarkMode {
         // Bg layer
         // Bg layer

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

@@ -110,7 +110,7 @@ impl Editor {
         //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 = "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.";
         //android::set_text(self.composer_id, atxt);
         //android::set_text(self.composer_id, atxt);
         //self.set_selection(2, 7);
         //self.set_selection(2, 7);
-        //self.refresh(&mut PropertyAtomicGuard::new());
+        //self.on_buffer_changed(&mut PropertyAtomicGuard::new()).await;
     }
     }
     /// Called on `AndroidSuggestEvent::CreateInputConnect`, which only happens after the View
     /// Called on `AndroidSuggestEvent::CreateInputConnect`, which only happens after the View
     /// is focused for the first time.
     /// is focused for the first time.

+ 71 - 83
bin/app/src/ui/chatedit.rs

@@ -429,23 +429,40 @@ impl ChatEdit {
 
 
     /// Inner position used for rendering
     /// Inner position used for rendering
     fn inner_pos(&self) -> Point {
     fn inner_pos(&self) -> Point {
+        let pad_top = self.padding_top();
+        let pad_bot = self.padding_bottom();
         let content_height = self.content_height.get();
         let content_height = self.content_height.get();
+        let outer_height = content_height + pad_top + pad_bot;
         let rect_h = self.rect.get_height();
         let rect_h = self.rect.get_height();
         let mut inner_pos = Point::zero();
         let mut inner_pos = Point::zero();
-        if content_height < rect_h {
+        if outer_height < rect_h {
+            // Min was applied to clip. Center content inside the rect.
             inner_pos.y = (rect_h - content_height) / 2.;
             inner_pos.y = (rect_h - content_height) / 2.;
+        } else {
+            inner_pos.y = pad_top;
         }
         }
         inner_pos
         inner_pos
     }
     }
 
 
     /// Maximum allowed scroll value
     /// Maximum allowed scroll value
+    /// * `content_height` measures the height of the actual content.
+    /// * `outer_height` applies the inner padding.
+    /// * `rect_h` then clips the `outer_height` to min/max values.
+    /// We only allow scrolling when max clipping has been applied.
     fn max_scroll(&self) -> f32 {
     fn max_scroll(&self) -> f32 {
         let content_height = self.content_height.get();
         let content_height = self.content_height.get();
+        let outer_height = content_height + self.padding_top() + self.padding_bottom();
         let rect_h = self.rect.get_height();
         let rect_h = self.rect.get_height();
-        let mut max_scroll = (content_height - rect_h).max(0.);
-        // Arbitrary little space at the bottom
-        max_scroll += self.font_size.get() / 3.;
-        max_scroll
+        //t!("max_scroll content_height={content_height}, rect_h={rect_h}");
+        (outer_height - rect_h).max(0.)
+    }
+
+    /// Gets the real cursor pos within the rect.
+    async fn get_cursor_pos(&self) -> Point {
+        // This is the position within the content.
+        let cursor_pos = self.lock_editor().await.get_cursor_pos();
+        // Apply the inner padding
+        cursor_pos + self.inner_pos()
     }
     }
 
 
     /// Lazy-initializes the editor and returns a handle to it
     /// Lazy-initializes the editor and returns a handle to it
@@ -913,62 +930,36 @@ impl ChatEdit {
         drop(editor);
         drop(editor);
 
 
         self.pause_blinking();
         self.pause_blinking();
+        self.finish_select(&mut PropertyAtomicGuard::new());
+    }
 
 
+    fn finish_select(&self, atom: &mut PropertyAtomicGuard) {
         self.is_phone_select.store(false, Ordering::Relaxed);
         self.is_phone_select.store(false, Ordering::Relaxed);
         self.hide_cursor.store(false, Ordering::Relaxed);
         self.hide_cursor.store(false, Ordering::Relaxed);
+        self.select_text
+            .clone()
+            .set_null(&mut PropertyAtomicGuard::new(), Role::Internal, 0)
+            .unwrap();
     }
     }
 
 
-    /// 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;
-        let cursor_width = self.cursor_width.get();
+    /// Whenever the cursor is modified this MUST be called
+    /// to recalculate the scroll y property.
+    async fn apply_cursor_scrolling(&self, atom: &mut PropertyAtomicGuard) {
         let mut scroll = self.scroll.get();
         let mut scroll = self.scroll.get();
-
-        let cursor_x = {
-            let font_size = self.font_size.get();
-            let window_scale = self.window_scale.get();
-            let baseline = self.baseline.get();
-            let glyphs = self.glyphs.lock().clone();
-
-            let mut glyph_pos_iter =
-                GlyphPositionIter::new(font_size, window_scale, &glyphs, baseline);
-
-            if cursor_pos == 0 {
-                0.
-            } else if cursor_pos == glyphs.len() {
-                let glyph_pos = glyph_pos_iter.last().unwrap();
-
-                let rhs = glyph_pos.rhs() + eol_nudge(font_size, &glyphs);
-                rhs
-            } else {
-                assert!(cursor_pos < glyphs.len());
-                let glyph_pos = glyph_pos_iter.nth(cursor_pos).expect("glyph pos mismatch glyphs");
-                glyph_pos.x
-            }
-        };
-
-        // The LHS and RHS of the cursor box
-        let cursor_lhs = cursor_x - scroll;
-        let cursor_rhs = cursor_lhs + cursor_width;
-
-        // RHS is outside
-        if cursor_rhs > rect.w {
-            // We want a scroll so RHS = w
-            // cursor_x - scroll + CURSOR_WIDTH = rect.w
-            scroll = cursor_x + cursor_width - rect.w;
-        // LHS is negative
-        } else if cursor_lhs < 0. {
-            // We want scroll so LHS = 0
-            // cursor_x - scroll = 0
-            scroll = cursor_x;
+        let rect_h = self.rect.get_height();
+        let cursor_y0 = self.get_cursor_pos().await.y;
+        let cursor_h = self.baseline.get() + self.cursor_descent.get();
+        // The bottom
+        let cursor_y1 = cursor_y0 + cursor_h;
+
+        if cursor_y1 > rect_h + scroll {
+            // We want cursor_y1 = rect_h + scroll by adjusting scroll
+            scroll = cursor_y1 - rect_h;
+            self.scroll.set(atom, scroll);
+        } else if cursor_y0 < scroll {
+            scroll = cursor_y0;
+            self.scroll.set(atom, scroll);
         }
         }
-
-        self.scroll.set(atom, scroll);
-        */
     }
     }
 
 
     fn pause_blinking(&self) {
     fn pause_blinking(&self) {
@@ -1046,19 +1037,13 @@ impl ChatEdit {
             return vec![]
             return vec![]
         }
         }
 
 
-        let lineheight = self.lineheight.get();
-
-        let mut cursor_instrs = vec![];
-
-        let mut cursor_pos = self.lock_editor().await.get_cursor_pos();
-        cursor_pos += self.inner_pos();
-        cursor_instrs.push(GfxDrawInstruction::Move(cursor_pos));
-
         let cursor_mesh =
         let cursor_mesh =
             self.cursor_mesh.lock().get_or_insert_with(|| self.regen_cursor_mesh()).clone();
             self.cursor_mesh.lock().get_or_insert_with(|| self.regen_cursor_mesh()).clone();
-        cursor_instrs.push(GfxDrawInstruction::Draw(cursor_mesh));
 
 
-        cursor_instrs
+        vec![
+            GfxDrawInstruction::Move(self.get_cursor_pos().await),
+            GfxDrawInstruction::Draw(cursor_mesh),
+        ]
     }
     }
 
 
     fn regen_bg_mesh(&self) -> Vec<GfxDrawInstruction> {
     fn regen_bg_mesh(&self) -> Vec<GfxDrawInstruction> {
@@ -1143,18 +1128,8 @@ impl ChatEdit {
         ]
         ]
     }
     }
 
 
-    fn bounded_height(&self, mut height: f32) -> f32 {
-        let min_height = self.min_height.get();
-        let max_height = self.max_height.get();
-
-        if height < min_height {
-            height = min_height;
-        }
-        if height > max_height {
-            height = max_height;
-        }
-
-        height
+    fn bounded_height(&self, height: f32) -> f32 {
+        height.clamp(self.min_height.get(), self.max_height.get())
     }
     }
 
 
     async fn eval_rect(&self, atom: &mut PropertyAtomicGuard) {
     async fn eval_rect(&self, atom: &mut PropertyAtomicGuard) {
@@ -1348,30 +1323,43 @@ impl ChatEdit {
             return
             return
         }
         }
 
 
-        let mut editor = self.lock_editor().await;
         match ev {
         match ev {
             AndroidSuggestEvent::Init => {
             AndroidSuggestEvent::Init => {
+                let mut editor = self.lock_editor().await;
                 editor.init();
                 editor.init();
                 // For debugging select, enable these and set a selection in the editor.
                 // For debugging select, enable these and set a selection in the editor.
                 //self.is_phone_select.store(true, Ordering::Relaxed);
                 //self.is_phone_select.store(true, Ordering::Relaxed);
                 //self.hide_cursor.store(true, Ordering::Relaxed);
                 //self.hide_cursor.store(true, Ordering::Relaxed);
+
+                // Debug code if we set text in editor.init()
+                //let atom = &mut PropertyAtomicGuard::new();
+                //editor.on_buffer_changed(atom).await;
                 return
                 return
             }
             }
-            AndroidSuggestEvent::CreateInputConnect => editor.setup(),
-            AndroidSuggestEvent::ComposeRegion { .. } | AndroidSuggestEvent::FinishCompose => {}
+            AndroidSuggestEvent::CreateInputConnect => {
+                let mut editor = self.lock_editor().await;
+                editor.setup();
+            }
             // Destructive text edits
             // Destructive text edits
+            AndroidSuggestEvent::ComposeRegion { .. } |
+            AndroidSuggestEvent::FinishCompose |
             AndroidSuggestEvent::Compose { .. } |
             AndroidSuggestEvent::Compose { .. } |
             AndroidSuggestEvent::DeleteSurroundingText { .. } => {
             AndroidSuggestEvent::DeleteSurroundingText { .. } => {
                 // Any editing will collapse selections
                 // Any editing will collapse selections
                 self.is_phone_select.store(false, Ordering::Relaxed);
                 self.is_phone_select.store(false, Ordering::Relaxed);
                 self.hide_cursor.store(false, Ordering::Relaxed);
                 self.hide_cursor.store(false, Ordering::Relaxed);
+
+                let atom = &mut PropertyAtomicGuard::new();
+                self.finish_select(atom);
+
+                let mut editor = self.lock_editor().await;
+                editor.on_buffer_changed(atom).await;
+                drop(editor);
+
+                self.apply_cursor_scrolling(atom).await;
             }
             }
         }
         }
 
 
-        let atom = &mut PropertyAtomicGuard::new();
-        editor.on_buffer_changed(atom).await;
-        drop(editor);
-
         // Only redraw once we have the parent_rect
         // Only redraw once we have the parent_rect
         // Can happen when we receive an Android event before the canvas is ready
         // Can happen when we receive an Android event before the canvas is ready
         if self.parent_rect.lock().is_some() {
         if self.parent_rect.lock().is_some() {