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

android: chatedit draw select handles on top so they aren't clipped by view rect

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

+ 4 - 0
bin/app/Makefile

@@ -75,11 +75,15 @@ dev: $(SRC) fonts
 
 
 apk: $(SRC) fonts
 apk: $(SRC) fonts
 	podman run -v $(shell pwd)/../../:/root/darkfi -w /root/darkfi/bin/app/ -t apk cargo quad-apk build $(DEV_FEATURES)
 	podman run -v $(shell pwd)/../../:/root/darkfi -w /root/darkfi/bin/app/ -t apk cargo quad-apk build $(DEV_FEATURES)
+	$(MAKE) install-apk
+
+install-apk:
 	-mv $(DEBUG_APK) .
 	-mv $(DEBUG_APK) .
 	-adb uninstall darkfi.darkfi_app
 	-adb uninstall darkfi.darkfi_app
 	adb install -r darkfi-app.apk
 	adb install -r darkfi-app.apk
 	reset
 	reset
 	adb logcat -c
 	adb logcat -c
+	adb shell monkey -p darkfi.darkfi_app -c android.intent.category.LAUNCHER 1
 	adb logcat -v color -s darkfi -s SAPP -s libc -s DEBUG -s ActivityManager -s ActivityTaskManager -s WindowManager -s AndroidRuntime -s rkfi.darkfi_app
 	adb logcat -v color -s darkfi -s SAPP -s libc -s DEBUG -s ActivityManager -s ActivityTaskManager -s WindowManager -s AndroidRuntime -s rkfi.darkfi_app
 
 
 # Useful for dev
 # Useful for dev

+ 4 - 0
bin/app/java/autosuggest/InvisibleInputView.java

@@ -89,6 +89,10 @@ public class InvisibleInputView extends View {
             | EditorInfo.IME_ACTION_GO;
             | EditorInfo.IME_ACTION_GO;
         outAttrs.initialSelStart = getSelectionStart();
         outAttrs.initialSelStart = getSelectionStart();
         outAttrs.initialSelEnd = getSelectionEnd();
         outAttrs.initialSelEnd = getSelectionEnd();
+        //if (outAttrs.initialSelStart != 0) {
+        //    Log.d("darkfi", "  select: [" + outAttrs.initialSelStart + ", " +
+        //                    outAttrs.initialSelEnd + "]");
+        //}
 
 
         inputConnection = new CustomInputConnection(id, editable, this);
         inputConnection = new CustomInputConnection(id, editable, this);
         onCreateInputConnect(id);
         onCreateInputConnect(id);

+ 8 - 6
bin/app/src/app/schema/chat.rs

@@ -54,12 +54,13 @@ use super::{ColorScheme, COLOR_SCHEME};
 mod android_ui_consts {
 mod android_ui_consts {
     use crate::gfx::{Point, Rectangle};
     use crate::gfx::{Point, Rectangle};
 
 
-    pub const CHANNEL_LABEL_BASELINE: f32 = 82.;
+    pub const CHANNEL_LABEL_Y: f32 = 30.;
+    pub const CHANNEL_LABEL_BASELINE: f32 = 30.;
     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 = 140.;
-    pub const CHATEDIT_MAX_HEIGHT: f32 = 1400.;
+    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.;
     pub const CHATEDIT_BOTTOM_PAD: f32 = 10.;
     pub const CHATEDIT_BOTTOM_PAD: f32 = 10.;
@@ -100,7 +101,7 @@ mod android_ui_consts {
     pub const ACTION_COPY_RECT: Rectangle = Rectangle::new(0., 0., 200., 160.);
     pub const ACTION_COPY_RECT: Rectangle = Rectangle::new(0., 0., 200., 160.);
     pub const ACTION_PASTE_RECT: Rectangle = Rectangle::new(220., 0., 240., 160.);
     pub const ACTION_PASTE_RECT: Rectangle = Rectangle::new(220., 0., 240., 160.);
     pub const ACTION_SELECT_ALL_RECT: Rectangle = Rectangle::new(480., 0., 400., 160.);
     pub const ACTION_SELECT_ALL_RECT: Rectangle = Rectangle::new(480., 0., 400., 160.);
-    pub const ACTION_LABEL_POS: Point = Point::new(40., 92.);
+    pub const ACTION_LABEL_POS: Point = Point::new(40., 40.);
 }
 }
 
 
 #[cfg(target_os = "android")]
 #[cfg(target_os = "android")]
@@ -121,6 +122,7 @@ mod ui_consts {
     use crate::gfx::{Point, Rectangle};
     use crate::gfx::{Point, Rectangle};
 
 
     // Chat UI
     // Chat UI
+    pub const CHANNEL_LABEL_Y: f32 = 30.;
     pub const CHANNEL_LABEL_BASELINE: f32 = 37.;
     pub const CHANNEL_LABEL_BASELINE: f32 = 37.;
     pub const BACKARROW_SCALE: f32 = 15.;
     pub const BACKARROW_SCALE: f32 = 15.;
     pub const BACKARROW_X: f32 = 38.;
     pub const BACKARROW_X: f32 = 38.;
@@ -342,11 +344,11 @@ pub async fn make(
     let node = create_text("channel_label");
     let node = create_text("channel_label");
     let prop = node.get_property("rect").unwrap();
     let prop = node.get_property("rect").unwrap();
     prop.clone().set_f32(atom, Role::App, 0, CHATEDIT_LHS_PAD).unwrap();
     prop.clone().set_f32(atom, Role::App, 0, CHATEDIT_LHS_PAD).unwrap();
-    prop.clone().set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.clone().set_f32(atom, Role::App, 1, CHANNEL_LABEL_Y).unwrap();
     prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
     prop.clone().set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
     prop.clone().set_f32(atom, Role::App, 3, CHATEDIT_HEIGHT).unwrap();
     prop.clone().set_f32(atom, Role::App, 3, CHATEDIT_HEIGHT).unwrap();
     node.set_property_f32(atom, Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
     node.set_property_f32(atom, Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
-    node.set_property_f32(atom, Role::App, "font_size", FONTSIZE).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", FONTSIZE * 1.2).unwrap();
     node.set_property_str(atom, Role::App, "text", &("#".to_string() + channel)).unwrap();
     node.set_property_str(atom, Role::App, "text", &("#".to_string() + channel)).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();
@@ -1337,7 +1339,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).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", FONTSIZE * 1.2).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();

+ 4 - 0
bin/app/src/gfx/linalg.rs

@@ -207,6 +207,10 @@ impl Rectangle {
         Some(clipped)
         Some(clipped)
     }
     }
 
 
+    pub fn with_zero_pos(&self) -> Self {
+        Self::new(0., 0., self.w, self.h)
+    }
+
     pub fn clip_point(&self, point: &mut Point) {
     pub fn clip_point(&self, point: &mut Point) {
         if point.x < self.x {
         if point.x < self.x {
             point.x = self.x;
             point.x = self.x;

+ 39 - 12
bin/app/src/gfx/mod.rs

@@ -272,8 +272,10 @@ impl GfxDrawMesh {
 pub enum GfxDrawInstruction {
 pub enum GfxDrawInstruction {
     SetScale(f32),
     SetScale(f32),
     Move(Point),
     Move(Point),
+    SetPos(Point),
     ApplyView(Rectangle),
     ApplyView(Rectangle),
     Draw(GfxDrawMesh),
     Draw(GfxDrawMesh),
+    EnableDebug,
 }
 }
 
 
 impl GfxDrawInstruction {
 impl GfxDrawInstruction {
@@ -285,8 +287,10 @@ impl GfxDrawInstruction {
         let instr = match self {
         let instr = match self {
             Self::SetScale(scale) => DrawInstruction::SetScale(scale),
             Self::SetScale(scale) => DrawInstruction::SetScale(scale),
             Self::Move(off) => DrawInstruction::Move(off),
             Self::Move(off) => DrawInstruction::Move(off),
+            Self::SetPos(pos) => DrawInstruction::SetPos(pos),
             Self::ApplyView(view) => DrawInstruction::ApplyView(view),
             Self::ApplyView(view) => DrawInstruction::ApplyView(view),
             Self::Draw(mesh) => DrawInstruction::Draw(mesh.compile(textures, buffers)?),
             Self::Draw(mesh) => DrawInstruction::Draw(mesh.compile(textures, buffers)?),
+            Self::EnableDebug => DrawInstruction::EnableDebug,
         };
         };
         Some(instr)
         Some(instr)
     }
     }
@@ -333,8 +337,10 @@ struct DrawMesh {
 enum DrawInstruction {
 enum DrawInstruction {
     SetScale(f32),
     SetScale(f32),
     Move(Point),
     Move(Point),
+    SetPos(Point),
     ApplyView(Rectangle),
     ApplyView(Rectangle),
     Draw(DrawMesh),
     Draw(DrawMesh),
+    EnableDebug,
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]
@@ -362,7 +368,7 @@ impl<'a> RenderContext<'a> {
             debug!(target: "gfx", "RenderContext::draw()");
             debug!(target: "gfx", "RenderContext::draw()");
         }
         }
         let curr_pos = Point::zero();
         let curr_pos = Point::zero();
-        self.draw_call(&self.draw_calls[&0], 0);
+        self.draw_call(&self.draw_calls[&0], 0, DEBUG_RENDER);
         if DEBUG_RENDER {
         if DEBUG_RENDER {
             debug!(target: "gfx", "RenderContext::draw() [DONE]");
             debug!(target: "gfx", "RenderContext::draw() [DONE]");
         }
         }
@@ -406,8 +412,8 @@ impl<'a> RenderContext<'a> {
         self.ctx.apply_uniforms_from_bytes(self.uniforms_data.as_ptr(), self.uniforms_data.len());
         self.ctx.apply_uniforms_from_bytes(self.uniforms_data.as_ptr(), self.uniforms_data.len());
     }
     }
 
 
-    fn draw_call(&mut self, draw_call: &DrawCall, indent: u32) {
-        let ws = if DEBUG_RENDER { " ".repeat(indent as usize * 4) } else { String::new() };
+    fn draw_call(&mut self, draw_call: &DrawCall, mut indent: u32, mut is_debug: bool) {
+        let mut ws = if is_debug { " ".repeat(indent as usize * 4) } else { String::new() };
 
 
         let old_scale = self.scale;
         let old_scale = self.scale;
         let old_view = self.view;
         let old_view = self.view;
@@ -417,13 +423,13 @@ impl<'a> RenderContext<'a> {
             match instr {
             match instr {
                 DrawInstruction::SetScale(scale) => {
                 DrawInstruction::SetScale(scale) => {
                     self.scale = *scale;
                     self.scale = *scale;
-                    if DEBUG_RENDER {
+                    if is_debug {
                         debug!(target: "gfx", "{ws}set_scale({scale})");
                         debug!(target: "gfx", "{ws}set_scale({scale})");
                     }
                     }
                 }
                 }
                 DrawInstruction::Move(off) => {
                 DrawInstruction::Move(off) => {
-                    self.cursor = old_cursor + *off;
-                    if DEBUG_RENDER {
+                    self.cursor += *off;
+                    if is_debug {
                         debug!(target: "gfx",
                         debug!(target: "gfx",
                             "{ws}move({off:?})  cursor={:?}, scale={}, view={:?}",
                             "{ws}move({off:?})  cursor={:?}, scale={}, view={:?}",
                             self.cursor, self.scale, self.view
                             self.cursor, self.scale, self.view
@@ -431,12 +437,22 @@ impl<'a> RenderContext<'a> {
                     }
                     }
                     self.apply_model();
                     self.apply_model();
                 }
                 }
+                DrawInstruction::SetPos(pos) => {
+                    self.cursor = old_cursor + *pos;
+                    if is_debug {
+                        debug!(target: "gfx",
+                            "{ws}set_pos({pos:?})  cursor={:?}, scale={}, view={:?}",
+                            self.cursor, self.scale, self.view
+                        );
+                    }
+                    self.apply_model();
+                }
                 DrawInstruction::ApplyView(view) => {
                 DrawInstruction::ApplyView(view) => {
                     // Adjust view relative to cursor
                     // Adjust view relative to cursor
                     self.view = *view + self.cursor;
                     self.view = *view + self.cursor;
                     // Cursor resets within the view
                     // Cursor resets within the view
                     self.cursor = Point::zero();
                     self.cursor = Point::zero();
-                    if DEBUG_RENDER {
+                    if is_debug {
                         debug!(target: "gfx",
                         debug!(target: "gfx",
                             "{ws}apply_view({view:?})  scale={}, view={:?}",
                             "{ws}apply_view({view:?})  scale={}, view={:?}",
                             self.scale, self.view
                             self.scale, self.view
@@ -446,7 +462,7 @@ impl<'a> RenderContext<'a> {
                     self.apply_model();
                     self.apply_model();
                 }
                 }
                 DrawInstruction::Draw(mesh) => {
                 DrawInstruction::Draw(mesh) => {
-                    if DEBUG_RENDER {
+                    if is_debug {
                         debug!(target: "gfx", "{ws}draw({mesh:?})");
                         debug!(target: "gfx", "{ws}draw({mesh:?})");
                     }
                     }
                     let texture = match mesh.texture {
                     let texture = match mesh.texture {
@@ -461,6 +477,13 @@ impl<'a> RenderContext<'a> {
                     self.ctx.apply_bindings(&bindings);
                     self.ctx.apply_bindings(&bindings);
                     self.ctx.draw(0, mesh.num_elements, 1);
                     self.ctx.draw(0, mesh.num_elements, 1);
                 }
                 }
+                DrawInstruction::EnableDebug => {
+                    if !is_debug {
+                        indent = 0;
+                    }
+                    is_debug = true;
+                    debug!(target: "gfx", "Frame start");
+                }
             }
             }
         }
         }
 
 
@@ -469,19 +492,23 @@ impl<'a> RenderContext<'a> {
         draw_calls.sort_unstable_by_key(|(_, dc)| dc.z_index);
         draw_calls.sort_unstable_by_key(|(_, dc)| dc.z_index);
 
 
         for (dc_key, dc) in draw_calls {
         for (dc_key, dc) in draw_calls {
-            if DEBUG_RENDER {
+            if is_debug {
                 debug!(target: "gfx", "{ws}drawcall {dc_key}");
                 debug!(target: "gfx", "{ws}drawcall {dc_key}");
             }
             }
-            self.draw_call(dc, indent + 1);
+            self.draw_call(dc, indent + 1, is_debug);
         }
         }
 
 
         self.scale = old_scale;
         self.scale = old_scale;
 
 
-        self.cursor = old_cursor;
-        self.apply_model();
+        if is_debug {
+            debug!(target: "gfx", "{ws}Frame close: cursor={old_cursor:?}, view={old_view:?}");
+        }
 
 
         self.view = old_view;
         self.view = old_view;
         self.apply_view();
         self.apply_view();
+
+        self.cursor = old_cursor;
+        self.apply_model();
     }
     }
 }
 }
 
 

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

@@ -22,6 +22,7 @@ use crate::{
     mesh::Color,
     mesh::Color,
     prop::{PropertyAtomicGuard, PropertyColor, PropertyFloat32, PropertyStr},
     prop::{PropertyAtomicGuard, PropertyColor, PropertyFloat32, PropertyStr},
     text2::{TextContext, TEXT_CTX},
     text2::{TextContext, TEXT_CTX},
+    AndroidSuggestEvent,
 };
 };
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::atomic::{AtomicBool, Ordering};
 
 
@@ -43,6 +44,7 @@ fn byte_to_char16_index(s: &str, byte_idx: usize) -> Option<usize> {
 
 
 pub struct Editor {
 pub struct Editor {
     pub composer_id: usize,
     pub composer_id: usize,
+    pub recvr: Option<async_channel::Receiver<AndroidSuggestEvent>>,
     is_init: bool,
     is_init: bool,
     is_setup: bool,
     is_setup: bool,
     /// We cannot receive focus until `AndroidSuggestEvent::Init` has finished.
     /// We cannot receive focus until `AndroidSuggestEvent::Init` has finished.
@@ -67,8 +69,13 @@ impl Editor {
         window_scale: PropertyFloat32,
         window_scale: PropertyFloat32,
         lineheight: PropertyFloat32,
         lineheight: PropertyFloat32,
     ) -> Self {
     ) -> Self {
+        let (sender, recvr) = async_channel::unbounded();
+        let composer_id = android::create_composer(sender);
+        t!("Created composer [{composer_id}]");
+
         Self {
         Self {
-            composer_id: usize::MAX,
+            composer_id,
+            recvr: Some(recvr),
             is_init: false,
             is_init: false,
             is_setup: false,
             is_setup: false,
             is_focus_req: AtomicBool::new(false),
             is_focus_req: AtomicBool::new(false),
@@ -94,6 +101,13 @@ impl Editor {
         if is_focus_req {
         if is_focus_req {
             android::focus(self.composer_id).unwrap();
             android::focus(self.composer_id).unwrap();
         }
         }
+
+        //android::focus(self.composer_id).unwrap();
+        //let atxt = "A berry is small 😊 and pulpy.";
+        //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);
+        //self.set_selection(2, 7);
+        //self.refresh(&mut PropertyAtomicGuard::new());
     }
     }
     /// 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.
@@ -103,8 +117,6 @@ impl Editor {
 
 
         assert!(self.composer_id != usize::MAX);
         assert!(self.composer_id != usize::MAX);
         t!("Initialized composer [{}]", self.composer_id);
         t!("Initialized composer [{}]", self.composer_id);
-        //let atxt = "A berry is small 😊 and pulpy.";
-        //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.";
     }
     }
 
 
     /// Can only be called after AndroidSuggestEvent::Init.
     /// Can only be called after AndroidSuggestEvent::Init.
@@ -220,6 +232,7 @@ impl Editor {
 
 
         let select_start = char16_to_byte_index(&edit.buffer, edit.select_start).unwrap();
         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 select_end = char16_to_byte_index(&edit.buffer, edit.select_end).unwrap();
+        //t!("selection() -> ({select_start}, {select_end})");
 
 
         let anchor = parley::Cursor::from_byte_index(
         let anchor = parley::Cursor::from_byte_index(
             &self.layout,
             &self.layout,

+ 2 - 2
bin/app/src/ui/button.rs

@@ -136,7 +136,7 @@ impl UIObject for Button {
     }
     }
 
 
     async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
     async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
-        t!("handle_touch({phase:?}, {id}, {touch_pos:?})");
+        //t!("handle_touch({phase:?}, {id}, {touch_pos:?})");
         if !self.is_active.get() {
         if !self.is_active.get() {
             return false
             return false
         }
         }
@@ -148,7 +148,7 @@ impl UIObject for Button {
 
 
         let rect = self.rect.get();
         let rect = self.rect.get();
         if !rect.contains(touch_pos) {
         if !rect.contains(touch_pos) {
-            t!("not inside rect");
+            //t!("not inside rect");
             return false
             return false
         }
         }
 
 

+ 134 - 91
bin/app/src/ui/chatedit.rs

@@ -213,16 +213,15 @@ pub struct ChatEdit {
     text_shaper: TextShaperPtr,
     text_shaper: TextShaperPtr,
     key_repeat: SyncMutex<PressedKeysSmoothRepeat>,
     key_repeat: SyncMutex<PressedKeysSmoothRepeat>,
 
 
-    glyphs: SyncMutex<Vec<Glyph>>,
+    // Moves the draw cursor and applies scroll
     root_dc_key: u64,
     root_dc_key: u64,
+    phone_select_handle_dc_key: u64,
+    // Applies the clipping view
     content_dc_key: u64,
     content_dc_key: u64,
-    /// DC key for the text
+    select_dc_key: u64,
     text_dc_key: u64,
     text_dc_key: u64,
-    cursor_mesh: SyncMutex<Option<GfxDrawMesh>>,
-    /// DC key for the cursor. Allows updating cursor independently.
     cursor_dc_key: u64,
     cursor_dc_key: u64,
-    /// DC key for the selection.
-    select_dc_key: u64,
+    cursor_mesh: SyncMutex<Option<GfxDrawMesh>>,
 
 
     is_active: PropertyBool,
     is_active: PropertyBool,
     is_focused: PropertyBool,
     is_focused: PropertyBool,
@@ -332,9 +331,6 @@ impl ChatEdit {
         let node_name = node_ref.name.clone();
         let node_name = node_ref.name.clone();
         let node_id = node_ref.id;
         let node_id = node_ref.id;
 
 
-        // Must do this whenever the text changes
-        let glyphs = text_shaper.shape(text.get(), font_size.get(), window_scale.get());
-
         let self_ = Arc::new(Self {
         let self_ = Arc::new(Self {
             node,
             node,
             tasks: OnceLock::new(),
             tasks: OnceLock::new(),
@@ -342,13 +338,13 @@ impl ChatEdit {
             text_shaper: text_shaper.clone(),
             text_shaper: text_shaper.clone(),
             key_repeat: SyncMutex::new(PressedKeysSmoothRepeat::new(400, 50)),
             key_repeat: SyncMutex::new(PressedKeysSmoothRepeat::new(400, 50)),
 
 
-            glyphs: SyncMutex::new(glyphs),
             root_dc_key: OsRng.gen(),
             root_dc_key: OsRng.gen(),
+            phone_select_handle_dc_key: OsRng.gen(),
             content_dc_key: OsRng.gen(),
             content_dc_key: OsRng.gen(),
+            select_dc_key: OsRng.gen(),
             text_dc_key: OsRng.gen(),
             text_dc_key: OsRng.gen(),
-            cursor_mesh: SyncMutex::new(None),
             cursor_dc_key: OsRng.gen(),
             cursor_dc_key: OsRng.gen(),
-            select_dc_key: OsRng.gen(),
+            cursor_mesh: SyncMutex::new(None),
 
 
             is_active,
             is_active,
             is_focused,
             is_focused,
@@ -462,6 +458,7 @@ impl ChatEdit {
     }
     }
 
 
     fn draw_phone_select_handle(&self, mesh: &mut MeshBuilder, pos: Point, side: f32) {
     fn draw_phone_select_handle(&self, mesh: &mut MeshBuilder, pos: Point, side: f32) {
+        let scroll = self.scroll.get();
         let baseline = self.baseline.get();
         let baseline = self.baseline.get();
         let select_ascent = self.select_ascent.get();
         let select_ascent = self.select_ascent.get();
         let handle_descent = self.handle_descent.get();
         let handle_descent = self.handle_descent.get();
@@ -473,6 +470,10 @@ impl ChatEdit {
         let x = pos.x;
         let x = pos.x;
         let mut y = pos.y + baseline;
         let mut y = pos.y + baseline;
 
 
+        if y - scroll < 0. {
+            return
+        }
+
         // Vertical line downwards. We use this instead of draw_box() so we have a fade.
         // Vertical line downwards. We use this instead of draw_box() so we have a fade.
         let verts = vec![
         let verts = vec![
             Vertex { pos: [x - side * 1., y - select_ascent], color: color_trans, uv: [0., 0.] },
             Vertex { pos: [x - side * 1., y - select_ascent], color: color_trans, uv: [0., 0.] },
@@ -705,16 +706,16 @@ impl ChatEdit {
     async fn handle_touch_start(&self, mut touch_pos: Point) -> bool {
     async fn handle_touch_start(&self, mut touch_pos: Point) -> bool {
         t!("handle_touch_start({touch_pos:?})");
         t!("handle_touch_start({touch_pos:?})");
 
 
-        if self.try_handle_drag(touch_pos).await {
-            return true
-        }
-
         let rect = self.rect.get();
         let rect = self.rect.get();
         if !rect.contains(touch_pos) {
         if !rect.contains(touch_pos) {
             t!("rect!cont rect={rect:?}, touch_pos={touch_pos:?}");
             t!("rect!cont rect={rect:?}, touch_pos={touch_pos:?}");
             return false
             return false
         }
         }
 
 
+        if self.try_handle_drag(touch_pos).await {
+            return true
+        }
+
         let mut touch_info = self.touch_info.lock();
         let mut touch_info = self.touch_info.lock();
         touch_info.start(touch_pos);
         touch_info.start(touch_pos);
         true
         true
@@ -729,42 +730,31 @@ impl ChatEdit {
             assert!(!self.is_phone_select.load(Ordering::Relaxed));
             assert!(!self.is_phone_select.load(Ordering::Relaxed));
             return None
             return None
         }
         }
-        assert!(self.is_phone_select.load(Ordering::Relaxed));
-
-        let mut first_pos = None;
-        let mut last_pos = Default::default();
-
-        sel.geometry_with(layout, |rect: parley::Rect, _| {
-            let rect = Rectangle::from(rect);
-            if first_pos.is_none() {
-                first_pos = Some(rect.bot_left());
-            }
-            last_pos = rect.corner();
-        });
 
 
-        let first_pos = first_pos.unwrap();
-        Some((first_pos, last_pos))
+        let first = Rectangle::from(sel.anchor().geometry(layout, 0.)).pos();
+        let last = Rectangle::from(sel.focus().geometry(layout, 0.)).pos();
+        Some((first, last))
     }
     }
 
 
     async fn try_handle_drag(&self, mut touch_pos: Point) -> bool {
     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
-        };
+        let Some((mut first, mut last)) = self.get_select_handles().await else { return false };
 
 
         self.abs_to_local(&mut touch_pos);
         self.abs_to_local(&mut touch_pos);
         t!("localize touch_pos = {touch_pos:?}");
         t!("localize touch_pos = {touch_pos:?}");
 
 
+        let baseline = self.baseline.get();
         let handle_off_y = self.handle_descent.get();
         let handle_off_y = self.handle_descent.get();
-        first_pos.y += handle_off_y;
-        last_pos.y += handle_off_y;
+
+        first.y += baseline + handle_off_y;
+        last.y += baseline + handle_off_y;
 
 
         // Are we within range of either one?
         // Are we within range of either one?
-        t!("handle center points = ({first_pos:?}, {last_pos:?})");
+        t!("handle center points = ({first:?}, {last:?})");
 
 
         const TOUCH_RADIUS_SQ: f32 = 10_000.;
         const TOUCH_RADIUS_SQ: f32 = 10_000.;
 
 
-        let first_dist_sq = first_pos.dist_sq(touch_pos);
-        let last_dist_sq = last_pos.dist_sq(touch_pos);
+        let first_dist_sq = first.dist_sq(touch_pos);
+        let last_dist_sq = last.dist_sq(touch_pos);
 
 
         let is_first = first_dist_sq <= TOUCH_RADIUS_SQ;
         let is_first = first_dist_sq <= TOUCH_RADIUS_SQ;
         let is_last = last_dist_sq <= TOUCH_RADIUS_SQ;
         let is_last = last_dist_sq <= TOUCH_RADIUS_SQ;
@@ -884,7 +874,7 @@ impl ChatEdit {
         true
         true
     }
     }
     async fn handle_touch_end(&self, mut touch_pos: Point) -> bool {
     async fn handle_touch_end(&self, mut touch_pos: Point) -> bool {
-        t!("handle_touch_end({touch_pos:?})");
+        //t!("handle_touch_end({touch_pos:?})");
         let atom = &mut PropertyAtomicGuard::new();
         let atom = &mut PropertyAtomicGuard::new();
         self.abs_to_local(&mut touch_pos);
         self.abs_to_local(&mut touch_pos);
 
 
@@ -990,14 +980,29 @@ impl ChatEdit {
         let rect = self.rect.get();
         let rect = self.rect.get();
         let scroll = self.scroll.get();
         let scroll = self.scroll.get();
 
 
-        let draw_main = vec![(
-            self.content_dc_key,
-            GfxDrawCall {
-                instrs: vec![GfxDrawInstruction::Move(Point::new(0., -scroll))],
-                dcs: vec![self.text_dc_key, self.cursor_dc_key, self.select_dc_key],
-                z_index: self.z_index.get(),
-            },
-        )];
+        let phone_sel_instrs = self.regen_phone_select_handle_mesh().await;
+
+        let mut content_instrs = vec![
+            GfxDrawInstruction::ApplyView(rect.with_zero_pos()),
+            GfxDrawInstruction::Move(Point::new(0., -scroll)),
+        ];
+        let mut bg_instrs = self.regen_bg_mesh();
+        content_instrs.append(&mut bg_instrs);
+
+        let draw_main = vec![
+            (
+                self.content_dc_key,
+                GfxDrawCall {
+                    instrs: content_instrs,
+                    dcs: vec![self.text_dc_key, self.cursor_dc_key, self.select_dc_key],
+                    z_index: 0,
+                },
+            ),
+            (
+                self.phone_select_handle_dc_key,
+                GfxDrawCall { instrs: phone_sel_instrs, dcs: vec![], z_index: 1 },
+            ),
+        ];
         self.render_api.replace_draw_calls(timest, draw_main);
         self.render_api.replace_draw_calls(timest, draw_main);
     }
     }
 
 
@@ -1011,9 +1016,15 @@ impl ChatEdit {
 
 
     async fn redraw_select(&self) {
     async fn redraw_select(&self) {
         let timest = unixtime();
         let timest = unixtime();
-        let instrs = self.regen_select_mesh().await;
-        let draw_calls =
-            vec![(self.select_dc_key, GfxDrawCall { instrs, dcs: vec![], z_index: 0 })];
+        let sel_instrs = self.regen_select_mesh().await;
+        let phone_sel_instrs = self.regen_phone_select_handle_mesh().await;
+        let draw_calls = vec![
+            (self.select_dc_key, GfxDrawCall { instrs: sel_instrs, dcs: vec![], z_index: 0 }),
+            (
+                self.phone_select_handle_dc_key,
+                GfxDrawCall { instrs: phone_sel_instrs, dcs: vec![], z_index: 1 },
+            ),
+        ];
         self.render_api.replace_draw_calls(timest, draw_calls);
         self.render_api.replace_draw_calls(timest, draw_calls);
     }
     }
 
 
@@ -1041,25 +1052,23 @@ impl ChatEdit {
     }
     }
 
 
     fn regen_bg_mesh(&self) -> Vec<GfxDrawInstruction> {
     fn regen_bg_mesh(&self) -> Vec<GfxDrawInstruction> {
+        if !self.debug.get() {
+            return vec![]
+        }
+
         let padding_top = self.padding_top();
         let padding_top = self.padding_top();
         let padding_bottom = self.padding_bottom();
         let padding_bottom = self.padding_bottom();
 
 
-        let mut instrs = vec![];
-        if self.debug.get() {
-            let mut rect = self.rect.get();
-            rect.x = 0.;
-            rect.y = 0.;
-            let mut mesh = MeshBuilder::new();
-            mesh.draw_outline(&rect, [0., 1., 0., 1.], 1.);
-            instrs.push(GfxDrawInstruction::Draw(mesh.alloc(&self.render_api).draw_untextured()));
+        let mut rect = self.rect.get().with_zero_pos();
 
 
-            rect.y = padding_top;
-            rect.h -= padding_top + padding_bottom;
-            let mut mesh = MeshBuilder::new();
-            mesh.draw_outline(&rect, [0., 1., 0., 0.5], 1.);
-            instrs.push(GfxDrawInstruction::Draw(mesh.alloc(&self.render_api).draw_untextured()));
-        }
-        instrs
+        let mut mesh = MeshBuilder::new();
+        mesh.draw_outline(&rect, [0., 1., 0., 1.], 1.);
+
+        rect.y = padding_top;
+        rect.h -= padding_top + padding_bottom;
+        mesh.draw_outline(&rect, [0., 1., 0., 0.5], 1.);
+
+        vec![GfxDrawInstruction::Draw(mesh.alloc(&self.render_api).draw_untextured())]
     }
     }
 
 
     async fn regen_txt_mesh(&self) -> Vec<GfxDrawInstruction> {
     async fn regen_txt_mesh(&self) -> Vec<GfxDrawInstruction> {
@@ -1085,31 +1094,44 @@ impl ChatEdit {
         let sel = editor.selection();
         let sel = editor.selection();
         let sel_color = self.hi_bg_color.get();
         let sel_color = self.hi_bg_color.get();
         if !sel.is_collapsed() {
         if !sel.is_collapsed() {
-            let mut first_pos = None;
-            let mut last_pos = Default::default();
-
             let mut mesh = MeshBuilder::new();
             let mut mesh = MeshBuilder::new();
             sel.geometry_with(layout, |rect: parley::Rect, _| {
             sel.geometry_with(layout, |rect: parley::Rect, _| {
-                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);
+                mesh.draw_filled_box(&rect.into(), 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()));
             instrs.push(GfxDrawInstruction::Draw(mesh.alloc(&self.render_api).draw_untextured()));
         }
         }
 
 
         instrs
         instrs
     }
     }
 
 
+    async fn regen_phone_select_handle_mesh(&self) -> Vec<GfxDrawInstruction> {
+        //t!("regen_phone_select_handle_mesh()");
+        let Some((mut first, mut last)) = self.get_select_handles().await else {
+            assert!(!self.is_phone_select.load(Ordering::Relaxed));
+            return vec![];
+        };
+
+        let scroll = self.scroll.get();
+
+        let editor = self.editor.lock().await;
+        let layout = editor.layout();
+
+        let sel = editor.selection();
+        assert!(!sel.is_collapsed());
+
+        let pos = self.inner_pos() + Point::new(0., -scroll);
+
+        // We could cache this and use Move instead but why bother?
+        let mut mesh = MeshBuilder::new();
+        self.draw_phone_select_handle(&mut mesh, first, -1.);
+        self.draw_phone_select_handle(&mut mesh, last, 1.);
+        vec![
+            GfxDrawInstruction::Move(pos),
+            GfxDrawInstruction::Draw(mesh.alloc(&self.render_api).draw_untextured()),
+        ]
+    }
+
     fn bounded_height(&self, mut height: f32) -> f32 {
     fn bounded_height(&self, mut height: f32) -> f32 {
         let min_height = self.min_height.get();
         let min_height = self.min_height.get();
         let max_height = self.max_height.get();
         let max_height = self.max_height.get();
@@ -1167,7 +1189,7 @@ impl ChatEdit {
     async fn make_draw_calls(&self, trace_id: u32, atom: &mut PropertyAtomicGuard) -> DrawUpdate {
     async fn make_draw_calls(&self, trace_id: u32, atom: &mut PropertyAtomicGuard) -> DrawUpdate {
         self.eval_rect(atom).await;
         self.eval_rect(atom).await;
 
 
-        let rect = self.rect.get();
+        let mut rect = self.rect.get();
         let max_scroll = self.max_scroll();
         let max_scroll = self.max_scroll();
         let mut scroll = self.scroll.get();
         let mut scroll = self.scroll.get();
         if scroll > max_scroll {
         if scroll > max_scroll {
@@ -1178,10 +1200,28 @@ impl ChatEdit {
         let cursor_instrs = self.get_cursor_instrs().await;
         let cursor_instrs = self.get_cursor_instrs().await;
         let txt_instrs = self.regen_txt_mesh().await;
         let txt_instrs = self.regen_txt_mesh().await;
         let sel_instrs = self.regen_select_mesh().await;
         let sel_instrs = self.regen_select_mesh().await;
+        let phone_sel_instrs = self.regen_phone_select_handle_mesh().await;
 
 
-        let mut root_instrs = vec![GfxDrawInstruction::ApplyView(rect)];
+        let mut content_instrs = vec![
+            GfxDrawInstruction::ApplyView(rect.with_zero_pos()),
+            GfxDrawInstruction::Move(Point::new(0., -scroll)),
+        ];
         let mut bg_instrs = self.regen_bg_mesh();
         let mut bg_instrs = self.regen_bg_mesh();
-        root_instrs.append(&mut bg_instrs);
+        content_instrs.append(&mut bg_instrs);
+
+        // + root (move)
+        // -+ content (apply view)
+        //  └╴select
+        //  └╴text
+        //  └╴cursor
+        // - phone_handle
+
+        // Why do we have such a complicated layout?
+        // Firstly phone select handles should never be clipped. So we must draw them outside
+        // of ApplyView.
+        // Then when adjusting selection, it's slow to redraw everything, so the selection
+        // must be drawn separately.
+        // Lastly the cursor is blinking and that's on top but with clipping.
 
 
         DrawUpdate {
         DrawUpdate {
             key: self.root_dc_key,
             key: self.root_dc_key,
@@ -1189,15 +1229,15 @@ impl ChatEdit {
                 (
                 (
                     self.root_dc_key,
                     self.root_dc_key,
                     GfxDrawCall {
                     GfxDrawCall {
-                        instrs: root_instrs,
-                        dcs: vec![self.content_dc_key],
+                        instrs: vec![GfxDrawInstruction::Move(rect.pos())],
+                        dcs: vec![self.content_dc_key, self.phone_select_handle_dc_key],
                         z_index: self.z_index.get(),
                         z_index: self.z_index.get(),
                     },
                     },
                 ),
                 ),
                 (
                 (
                     self.content_dc_key,
                     self.content_dc_key,
                     GfxDrawCall {
                     GfxDrawCall {
-                        instrs: vec![GfxDrawInstruction::Move(Point::new(0., -scroll))],
+                        instrs: content_instrs,
                         dcs: vec![self.text_dc_key, self.cursor_dc_key, self.select_dc_key],
                         dcs: vec![self.text_dc_key, self.cursor_dc_key, self.select_dc_key],
                         z_index: 0,
                         z_index: 0,
                     },
                     },
@@ -1208,6 +1248,10 @@ impl ChatEdit {
                     self.cursor_dc_key,
                     self.cursor_dc_key,
                     GfxDrawCall { instrs: cursor_instrs, dcs: vec![], z_index: 2 },
                     GfxDrawCall { instrs: cursor_instrs, dcs: vec![], z_index: 2 },
                 ),
                 ),
+                (
+                    self.phone_select_handle_dc_key,
+                    GfxDrawCall { instrs: phone_sel_instrs, dcs: vec![], z_index: 1 },
+                ),
             ],
             ],
         }
         }
     }
     }
@@ -1272,6 +1316,9 @@ impl ChatEdit {
         match ev {
         match ev {
             AndroidSuggestEvent::Init => {
             AndroidSuggestEvent::Init => {
                 editor.init();
                 editor.init();
+                // For debugging select, enable these and set a selection in the editor.
+                //self.is_phone_select.store(true, Ordering::Relaxed);
+                //self.hide_cursor.store(true, Ordering::Relaxed);
                 return
                 return
             }
             }
             AndroidSuggestEvent::CreateInputConnect => editor.setup(),
             AndroidSuggestEvent::CreateInputConnect => editor.setup(),
@@ -1412,11 +1459,7 @@ impl UIObject for ChatEdit {
 
 
         #[cfg(target_os = "android")]
         #[cfg(target_os = "android")]
         {
         {
-            let (sender, recvr) = async_channel::unbounded();
-            let composer_id = crate::android::create_composer(sender);
-            self.editor.lock().await.composer_id = composer_id;
-            t!("Created composer [{composer_id}]");
-
+            let recvr = self.editor.lock().await.recvr.take().unwrap();
             let me2 = me.clone();
             let me2 = me.clone();
             let autosuggest_task = ex.spawn(async move {
             let autosuggest_task = ex.spawn(async move {
                 loop {
                 loop {

+ 1 - 1
bin/app/src/ui/gesture.rs

@@ -89,7 +89,7 @@ impl UIObject for Gesture {
     }
     }
 
 
     async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
     async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
-        t!("handle_touch({phase:?}, {id}, {touch_pos:?})");
+        //t!("handle_touch({phase:?}, {id}, {touch_pos:?})");
         let id = id as usize;
         let id = id as usize;
         if id >= MAX_TOUCH {
         if id >= MAX_TOUCH {
             return false
             return false