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

This commit is contained in:
darkfi
2025-04-24 14:20:09 +02:00
parent c8003d98b8
commit 37b7cd3c43
9 changed files with 214 additions and 117 deletions

View File

@@ -75,11 +75,15 @@ dev: $(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)
$(MAKE) install-apk
install-apk:
-mv $(DEBUG_APK) .
-adb uninstall darkfi.darkfi_app
adb install -r darkfi-app.apk
reset
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
# Useful for dev

View File

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

View File

@@ -54,12 +54,13 @@ use super::{ColorScheme, COLOR_SCHEME};
mod android_ui_consts {
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_X: f32 = 50.;
pub const BACKARROW_Y: f32 = 70.;
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_SINGLE_LINE_Y: f32 = 120.;
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_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_LABEL_POS: Point = Point::new(40., 92.);
pub const ACTION_LABEL_POS: Point = Point::new(40., 40.);
}
#[cfg(target_os = "android")]
@@ -121,6 +122,7 @@ mod ui_consts {
use crate::gfx::{Point, Rectangle};
// Chat UI
pub const CHANNEL_LABEL_Y: f32 = 30.;
pub const CHANNEL_LABEL_BASELINE: f32 = 37.;
pub const BACKARROW_SCALE: f32 = 15.;
pub const BACKARROW_X: f32 = 38.;
@@ -342,11 +344,11 @@ pub async fn make(
let node = create_text("channel_label");
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, 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_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, "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_bool(atom, Role::App, "debug", true).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, 3, ACTION_SELECT_ALL_RECT.h).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_bool(atom, Role::App, "debug", true).unwrap();
//node.set_property_str(atom, Role::App, "text", "anon1").unwrap();

View File

@@ -207,6 +207,10 @@ impl Rectangle {
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) {
if point.x < self.x {
point.x = self.x;

View File

@@ -272,8 +272,10 @@ impl GfxDrawMesh {
pub enum GfxDrawInstruction {
SetScale(f32),
Move(Point),
SetPos(Point),
ApplyView(Rectangle),
Draw(GfxDrawMesh),
EnableDebug,
}
impl GfxDrawInstruction {
@@ -285,8 +287,10 @@ impl GfxDrawInstruction {
let instr = match self {
Self::SetScale(scale) => DrawInstruction::SetScale(scale),
Self::Move(off) => DrawInstruction::Move(off),
Self::SetPos(pos) => DrawInstruction::SetPos(pos),
Self::ApplyView(view) => DrawInstruction::ApplyView(view),
Self::Draw(mesh) => DrawInstruction::Draw(mesh.compile(textures, buffers)?),
Self::EnableDebug => DrawInstruction::EnableDebug,
};
Some(instr)
}
@@ -333,8 +337,10 @@ struct DrawMesh {
enum DrawInstruction {
SetScale(f32),
Move(Point),
SetPos(Point),
ApplyView(Rectangle),
Draw(DrawMesh),
EnableDebug,
}
#[derive(Debug)]
@@ -362,7 +368,7 @@ impl<'a> RenderContext<'a> {
debug!(target: "gfx", "RenderContext::draw()");
}
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 {
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());
}
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_view = self.view;
@@ -417,13 +423,13 @@ impl<'a> RenderContext<'a> {
match instr {
DrawInstruction::SetScale(scale) => {
self.scale = *scale;
if DEBUG_RENDER {
if is_debug {
debug!(target: "gfx", "{ws}set_scale({scale})");
}
}
DrawInstruction::Move(off) => {
self.cursor = old_cursor + *off;
if DEBUG_RENDER {
self.cursor += *off;
if is_debug {
debug!(target: "gfx",
"{ws}move({off:?}) cursor={:?}, scale={}, view={:?}",
self.cursor, self.scale, self.view
@@ -431,12 +437,22 @@ impl<'a> RenderContext<'a> {
}
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) => {
// Adjust view relative to cursor
self.view = *view + self.cursor;
// Cursor resets within the view
self.cursor = Point::zero();
if DEBUG_RENDER {
if is_debug {
debug!(target: "gfx",
"{ws}apply_view({view:?}) scale={}, view={:?}",
self.scale, self.view
@@ -446,7 +462,7 @@ impl<'a> RenderContext<'a> {
self.apply_model();
}
DrawInstruction::Draw(mesh) => {
if DEBUG_RENDER {
if is_debug {
debug!(target: "gfx", "{ws}draw({mesh:?})");
}
let texture = match mesh.texture {
@@ -461,6 +477,13 @@ impl<'a> RenderContext<'a> {
self.ctx.apply_bindings(&bindings);
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);
for (dc_key, dc) in draw_calls {
if DEBUG_RENDER {
if is_debug {
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.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.apply_view();
self.cursor = old_cursor;
self.apply_model();
}
}

View File

@@ -22,6 +22,7 @@ use crate::{
mesh::Color,
prop::{PropertyAtomicGuard, PropertyColor, PropertyFloat32, PropertyStr},
text2::{TextContext, TEXT_CTX},
AndroidSuggestEvent,
};
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 composer_id: usize,
pub recvr: Option<async_channel::Receiver<AndroidSuggestEvent>>,
is_init: bool,
is_setup: bool,
/// We cannot receive focus until `AndroidSuggestEvent::Init` has finished.
@@ -67,8 +69,13 @@ impl Editor {
window_scale: PropertyFloat32,
lineheight: PropertyFloat32,
) -> Self {
let (sender, recvr) = async_channel::unbounded();
let composer_id = android::create_composer(sender);
t!("Created composer [{composer_id}]");
Self {
composer_id: usize::MAX,
composer_id,
recvr: Some(recvr),
is_init: false,
is_setup: false,
is_focus_req: AtomicBool::new(false),
@@ -94,6 +101,13 @@ impl Editor {
if is_focus_req {
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
/// is focused for the first time.
@@ -103,8 +117,6 @@ impl Editor {
assert!(self.composer_id != usize::MAX);
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.
@@ -220,6 +232,7 @@ impl Editor {
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();
//t!("selection() -> ({select_start}, {select_end})");
let anchor = parley::Cursor::from_byte_index(
&self.layout,

View File

@@ -136,7 +136,7 @@ impl UIObject for Button {
}
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() {
return false
}
@@ -148,7 +148,7 @@ impl UIObject for Button {
let rect = self.rect.get();
if !rect.contains(touch_pos) {
t!("not inside rect");
//t!("not inside rect");
return false
}

View File

@@ -213,16 +213,15 @@ pub struct ChatEdit {
text_shaper: TextShaperPtr,
key_repeat: SyncMutex<PressedKeysSmoothRepeat>,
glyphs: SyncMutex<Vec<Glyph>>,
// Moves the draw cursor and applies scroll
root_dc_key: u64,
phone_select_handle_dc_key: u64,
// Applies the clipping view
content_dc_key: u64,
/// DC key for the text
text_dc_key: u64,
cursor_mesh: SyncMutex<Option<GfxDrawMesh>>,
/// DC key for the cursor. Allows updating cursor independently.
cursor_dc_key: u64,
/// DC key for the selection.
select_dc_key: u64,
text_dc_key: u64,
cursor_dc_key: u64,
cursor_mesh: SyncMutex<Option<GfxDrawMesh>>,
is_active: PropertyBool,
is_focused: PropertyBool,
@@ -332,9 +331,6 @@ impl ChatEdit {
let node_name = node_ref.name.clone();
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 {
node,
tasks: OnceLock::new(),
@@ -342,13 +338,13 @@ impl ChatEdit {
text_shaper: text_shaper.clone(),
key_repeat: SyncMutex::new(PressedKeysSmoothRepeat::new(400, 50)),
glyphs: SyncMutex::new(glyphs),
root_dc_key: OsRng.gen(),
phone_select_handle_dc_key: OsRng.gen(),
content_dc_key: OsRng.gen(),
text_dc_key: OsRng.gen(),
cursor_mesh: SyncMutex::new(None),
cursor_dc_key: OsRng.gen(),
select_dc_key: OsRng.gen(),
text_dc_key: OsRng.gen(),
cursor_dc_key: OsRng.gen(),
cursor_mesh: SyncMutex::new(None),
is_active,
is_focused,
@@ -462,6 +458,7 @@ impl ChatEdit {
}
fn draw_phone_select_handle(&self, mesh: &mut MeshBuilder, pos: Point, side: f32) {
let scroll = self.scroll.get();
let baseline = self.baseline.get();
let select_ascent = self.select_ascent.get();
let handle_descent = self.handle_descent.get();
@@ -473,6 +470,10 @@ impl ChatEdit {
let x = pos.x;
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.
let verts = vec![
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 {
t!("handle_touch_start({touch_pos:?})");
if self.try_handle_drag(touch_pos).await {
return true
}
let rect = self.rect.get();
if !rect.contains(touch_pos) {
t!("rect!cont rect={rect:?}, touch_pos={touch_pos:?}");
return false
}
if self.try_handle_drag(touch_pos).await {
return true
}
let mut touch_info = self.touch_info.lock();
touch_info.start(touch_pos);
true
@@ -729,42 +730,31 @@ impl ChatEdit {
assert!(!self.is_phone_select.load(Ordering::Relaxed));
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 {
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);
t!("localize touch_pos = {touch_pos:?}");
let baseline = self.baseline.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?
t!("handle center points = ({first_pos:?}, {last_pos:?})");
t!("handle center points = ({first:?}, {last:?})");
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_last = last_dist_sq <= TOUCH_RADIUS_SQ;
@@ -884,7 +874,7 @@ impl ChatEdit {
true
}
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();
self.abs_to_local(&mut touch_pos);
@@ -990,14 +980,29 @@ impl ChatEdit {
let rect = self.rect.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);
}
@@ -1011,9 +1016,15 @@ impl ChatEdit {
async fn redraw_select(&self) {
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);
}
@@ -1041,25 +1052,23 @@ impl ChatEdit {
}
fn regen_bg_mesh(&self) -> Vec<GfxDrawInstruction> {
if !self.debug.get() {
return vec![]
}
let padding_top = self.padding_top();
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> {
@@ -1085,31 +1094,44 @@ impl ChatEdit {
let sel = editor.selection();
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::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
}
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 {
let min_height = self.min_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 {
self.eval_rect(atom).await;
let rect = self.rect.get();
let mut rect = self.rect.get();
let max_scroll = self.max_scroll();
let mut scroll = self.scroll.get();
if scroll > max_scroll {
@@ -1178,10 +1200,28 @@ impl ChatEdit {
let cursor_instrs = self.get_cursor_instrs().await;
let txt_instrs = self.regen_txt_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();
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 {
key: self.root_dc_key,
@@ -1189,15 +1229,15 @@ impl ChatEdit {
(
self.root_dc_key,
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(),
},
),
(
self.content_dc_key,
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],
z_index: 0,
},
@@ -1208,6 +1248,10 @@ impl ChatEdit {
self.cursor_dc_key,
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 {
AndroidSuggestEvent::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
}
AndroidSuggestEvent::CreateInputConnect => editor.setup(),
@@ -1412,11 +1459,7 @@ impl UIObject for ChatEdit {
#[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 autosuggest_task = ex.spawn(async move {
loop {

View File

@@ -89,7 +89,7 @@ impl UIObject for Gesture {
}
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;
if id >= MAX_TOUCH {
return false