mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
app: remove old text input stuff and replace with new game text input
This commit is contained in:
@@ -20,17 +20,6 @@ use miniquad::native::android::{self, ndk_sys, ndk_utils};
|
||||
use parking_lot::Mutex as SyncMutex;
|
||||
use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
|
||||
|
||||
// TODO: Remove this enum after migration to GameTextInput is complete
|
||||
#[derive(Debug)]
|
||||
pub enum AndroidSuggestEvent {
|
||||
Init,
|
||||
CreateInputConnect,
|
||||
Compose { text: String, cursor_pos: i32, is_commit: bool },
|
||||
ComposeRegion { start: usize, end: usize },
|
||||
FinishCompose,
|
||||
DeleteSurroundingText { left: usize, right: usize },
|
||||
}
|
||||
|
||||
pub mod insets;
|
||||
pub mod textinput;
|
||||
pub mod vid;
|
||||
@@ -75,274 +64,6 @@ macro_rules! call_mainactivity_bool_method {
|
||||
}};
|
||||
}
|
||||
|
||||
struct GlobalData {
|
||||
senders: HashMap<usize, async_channel::Sender<AndroidSuggestEvent>>,
|
||||
next_id: usize,
|
||||
}
|
||||
|
||||
fn send(id: usize, ev: AndroidSuggestEvent) {
|
||||
let globals = &GLOBALS.lock();
|
||||
let Some(sender) = globals.senders.get(&id) else {
|
||||
warn!(target: "android", "Unknown composer_id={id} discard ev: {ev:?}");
|
||||
return
|
||||
};
|
||||
let _ = sender.try_send(ev);
|
||||
}
|
||||
|
||||
unsafe impl Send for GlobalData {}
|
||||
unsafe impl Sync for GlobalData {}
|
||||
|
||||
static GLOBALS: LazyLock<SyncMutex<GlobalData>> =
|
||||
LazyLock::new(|| SyncMutex::new(GlobalData { senders: HashMap::new(), next_id: 0 }));
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_darkfi_darkfi_1app_MainActivity_onInitEdit(
|
||||
_env: *mut ndk_sys::JNIEnv,
|
||||
_: ndk_sys::jobject,
|
||||
id: ndk_sys::jint,
|
||||
) {
|
||||
assert!(id >= 0);
|
||||
let id = id as usize;
|
||||
send(id, AndroidSuggestEvent::Init);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_autosuggest_InvisibleInputView_onCreateInputConnect(
|
||||
_env: *mut ndk_sys::JNIEnv,
|
||||
_: ndk_sys::jobject,
|
||||
id: ndk_sys::jint,
|
||||
) {
|
||||
assert!(id >= 0);
|
||||
let id = id as usize;
|
||||
send(id, AndroidSuggestEvent::CreateInputConnect);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onCompose(
|
||||
env: *mut ndk_sys::JNIEnv,
|
||||
_: ndk_sys::jobject,
|
||||
id: ndk_sys::jint,
|
||||
text: ndk_sys::jobject,
|
||||
cursor_pos: ndk_sys::jint,
|
||||
is_commit: ndk_sys::jboolean,
|
||||
) {
|
||||
assert!(id >= 0);
|
||||
let id = id as usize;
|
||||
let text = ndk_utils::get_utf_str!(env, text);
|
||||
send(
|
||||
id,
|
||||
AndroidSuggestEvent::Compose {
|
||||
text: text.to_string(),
|
||||
cursor_pos,
|
||||
is_commit: is_commit == 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onSetComposeRegion(
|
||||
_env: *mut ndk_sys::JNIEnv,
|
||||
_: ndk_sys::jobject,
|
||||
id: ndk_sys::jint,
|
||||
start: ndk_sys::jint,
|
||||
end: ndk_sys::jint,
|
||||
) {
|
||||
assert!(id >= 0);
|
||||
let id = id as usize;
|
||||
send(id, AndroidSuggestEvent::ComposeRegion { start: start as usize, end: end as usize });
|
||||
}
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onFinishCompose(
|
||||
_env: *mut ndk_sys::JNIEnv,
|
||||
_: ndk_sys::jobject,
|
||||
id: ndk_sys::jint,
|
||||
) {
|
||||
assert!(id >= 0);
|
||||
let id = id as usize;
|
||||
send(id, AndroidSuggestEvent::FinishCompose);
|
||||
}
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onDeleteSurroundingText(
|
||||
_env: *mut ndk_sys::JNIEnv,
|
||||
_: ndk_sys::jobject,
|
||||
id: ndk_sys::jint,
|
||||
left: ndk_sys::jint,
|
||||
right: ndk_sys::jint,
|
||||
) {
|
||||
assert!(id >= 0);
|
||||
let id = id as usize;
|
||||
send(
|
||||
id,
|
||||
AndroidSuggestEvent::DeleteSurroundingText { left: left as usize, right: right as usize },
|
||||
);
|
||||
}
|
||||
|
||||
pub fn create_composer(sender: async_channel::Sender<AndroidSuggestEvent>) -> usize {
|
||||
let composer_id = {
|
||||
let mut globals = GLOBALS.lock();
|
||||
let id = globals.next_id;
|
||||
globals.next_id += 1;
|
||||
globals.senders.insert(id, sender);
|
||||
id
|
||||
};
|
||||
unsafe {
|
||||
let env = android::attach_jni_env();
|
||||
ndk_utils::call_void_method!(env, android::ACTIVITY, "createComposer", "(I)V", composer_id);
|
||||
}
|
||||
composer_id
|
||||
}
|
||||
|
||||
pub fn focus(id: usize) -> Option<()> {
|
||||
let is_success = unsafe {
|
||||
let env = android::attach_jni_env();
|
||||
|
||||
ndk_utils::call_bool_method!(env, android::ACTIVITY, "focus", "(I)Z", id as i32)
|
||||
};
|
||||
if is_success == 0u8 {
|
||||
None
|
||||
} else {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
pub fn unfocus(id: usize) -> Option<()> {
|
||||
let is_success = unsafe {
|
||||
let env = android::attach_jni_env();
|
||||
|
||||
ndk_utils::call_bool_method!(env, android::ACTIVITY, "unfocus", "(I)Z", id as i32)
|
||||
};
|
||||
if is_success == 0u8 {
|
||||
None
|
||||
} else {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(id: usize, text: &str) -> Option<()> {
|
||||
let ctext = std::ffi::CString::new(text).unwrap();
|
||||
let is_success = unsafe {
|
||||
let env = android::attach_jni_env();
|
||||
|
||||
let new_string_utf = (**env).NewStringUTF.unwrap();
|
||||
let jtext = new_string_utf(env, ctext.as_ptr());
|
||||
let delete_local_ref = (**env).DeleteLocalRef.unwrap();
|
||||
|
||||
let res = ndk_utils::call_bool_method!(
|
||||
env,
|
||||
android::ACTIVITY,
|
||||
"setText",
|
||||
"(ILjava/lang/String;)Z",
|
||||
id as i32,
|
||||
jtext
|
||||
);
|
||||
delete_local_ref(env, jtext);
|
||||
res
|
||||
};
|
||||
if is_success == 0u8 {
|
||||
None
|
||||
} else {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_selection(id: usize, select_start: usize, select_end: usize) -> Option<()> {
|
||||
//trace!(target: "android", "set_selection({id}, {select_start}, {select_end})");
|
||||
let is_success = unsafe {
|
||||
let env = android::attach_jni_env();
|
||||
ndk_utils::call_bool_method!(
|
||||
env,
|
||||
android::ACTIVITY,
|
||||
"setSelection",
|
||||
"(III)Z",
|
||||
id as i32,
|
||||
select_start as i32,
|
||||
select_end as i32
|
||||
)
|
||||
};
|
||||
if is_success == 0u8 {
|
||||
None
|
||||
} else {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commit_text(id: usize, text: &str) -> Option<()> {
|
||||
let ctext = std::ffi::CString::new(text).unwrap();
|
||||
let is_success = unsafe {
|
||||
let env = android::attach_jni_env();
|
||||
|
||||
let new_string_utf = (**env).NewStringUTF.unwrap();
|
||||
let delete_local_ref = (**env).DeleteLocalRef.unwrap();
|
||||
|
||||
let jtext = new_string_utf(env, ctext.as_ptr());
|
||||
|
||||
let res = ndk_utils::call_bool_method!(
|
||||
env,
|
||||
android::ACTIVITY,
|
||||
"commitText",
|
||||
"(ILjava/lang/String;)Z",
|
||||
id as i32,
|
||||
jtext
|
||||
);
|
||||
delete_local_ref(env, jtext);
|
||||
res
|
||||
};
|
||||
if is_success == 0u8 {
|
||||
None
|
||||
} else {
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Editable {
|
||||
pub buffer: String,
|
||||
pub select_start: usize,
|
||||
pub select_end: usize,
|
||||
pub compose_start: Option<usize>,
|
||||
pub compose_end: Option<usize>,
|
||||
}
|
||||
|
||||
pub fn get_editable(id: usize) -> Option<Editable> {
|
||||
//trace!(target: "android", "get_editable({id})");
|
||||
unsafe {
|
||||
let env = android::attach_jni_env();
|
||||
let input_view = ndk_utils::call_object_method!(
|
||||
env,
|
||||
android::ACTIVITY,
|
||||
"getInputView",
|
||||
"(I)Lautosuggest/InvisibleInputView;",
|
||||
id as i32
|
||||
);
|
||||
if input_view.is_null() {
|
||||
return None
|
||||
}
|
||||
|
||||
let buffer =
|
||||
ndk_utils::call_object_method!(env, input_view, "rawText", "()Ljava/lang/String;");
|
||||
assert!(!buffer.is_null());
|
||||
let buffer = ndk_utils::get_utf_str!(env, buffer).to_string();
|
||||
|
||||
let select_start = ndk_utils::call_int_method!(env, input_view, "getSelectionStart", "()I");
|
||||
|
||||
let select_end = ndk_utils::call_int_method!(env, input_view, "getSelectionEnd", "()I");
|
||||
|
||||
let compose_start = ndk_utils::call_int_method!(env, input_view, "getComposeStart", "()I");
|
||||
|
||||
let compose_end = ndk_utils::call_int_method!(env, input_view, "getComposeEnd", "()I");
|
||||
|
||||
assert!(select_start >= 0);
|
||||
assert!(select_end >= 0);
|
||||
assert!(compose_start >= 0 || compose_start == compose_end);
|
||||
assert!(compose_start <= compose_end);
|
||||
|
||||
Some(Editable {
|
||||
buffer,
|
||||
select_start: select_start as usize,
|
||||
select_end: select_end as usize,
|
||||
compose_start: if compose_start < 0 { None } else { Some(compose_start as usize) },
|
||||
compose_end: if compose_end < 0 { None } else { Some(compose_end as usize) },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_appdata_path() -> PathBuf {
|
||||
call_mainactivity_str_method!("getAppDataPath").into()
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@ pub struct AndroidTextInput {
|
||||
state: *mut GameTextInput,
|
||||
}
|
||||
|
||||
// SAFETY: GameTextInput is accessed synchronously through show_ime/hide_ime/set_state/get_state
|
||||
// The pointer is valid for the lifetime of the AndroidTextInput instance and is only
|
||||
// accessed from the thread that owns the AndroidTextInput.
|
||||
unsafe impl Send for AndroidTextInput {}
|
||||
unsafe impl Sync for AndroidTextInput {}
|
||||
|
||||
impl AndroidTextInput {
|
||||
pub fn new(sender: async_channel::Sender<AndroidTextInputState>) -> Self {
|
||||
let id = {
|
||||
|
||||
@@ -17,40 +17,22 @@
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
android::{self, AndroidSuggestEvent},
|
||||
android::{
|
||||
self,
|
||||
textinput::{AndroidTextInput, AndroidTextInputState},
|
||||
},
|
||||
gfx::Point,
|
||||
mesh::Color,
|
||||
prop::{PropertyAtomicGuard, PropertyColor, PropertyFloat32, PropertyStr},
|
||||
text2::{TextContext, TEXT_CTX},
|
||||
};
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use std::cmp::{max, min};
|
||||
|
||||
macro_rules! t { ($($arg:tt)*) => { trace!(target: "text::editor::android", $($arg)*); } }
|
||||
|
||||
// You must be careful working with string indexes in Java. They are UTF16 string indexs, not UTF8
|
||||
fn char16_to_byte_index(s: &str, char_idx: usize) -> Option<usize> {
|
||||
let utf16_data: Vec<_> = s.encode_utf16().take(char_idx).collect();
|
||||
let prestr = String::from_utf16(&utf16_data).ok()?;
|
||||
Some(prestr.len())
|
||||
}
|
||||
fn byte_to_char16_index(s: &str, byte_idx: usize) -> Option<usize> {
|
||||
if byte_idx > s.len() || !s.is_char_boundary(byte_idx) {
|
||||
return None
|
||||
}
|
||||
Some(s[..byte_idx].encode_utf16().count())
|
||||
}
|
||||
|
||||
pub struct Editor {
|
||||
pub composer_id: usize,
|
||||
pub recvr: async_channel::Receiver<AndroidSuggestEvent>,
|
||||
is_init: bool,
|
||||
is_setup: bool,
|
||||
/// We cannot receive focus until `AndroidSuggestEvent::Init` has finished.
|
||||
/// We use this flag to delay calling `android::focus()` until the init has completed.
|
||||
is_focus_req: AtomicBool,
|
||||
input: AndroidTextInput,
|
||||
pub recvr: async_channel::Receiver<AndroidTextInputState>,
|
||||
|
||||
layout: parley::Layout<Color>,
|
||||
width: Option<f32>,
|
||||
@@ -71,15 +53,9 @@ impl Editor {
|
||||
lineheight: PropertyFloat32,
|
||||
) -> Self {
|
||||
let (sender, recvr) = async_channel::unbounded();
|
||||
let composer_id = android::create_composer(sender);
|
||||
t!("Created composer [{composer_id}]");
|
||||
|
||||
Self {
|
||||
composer_id,
|
||||
input: AndroidTextInput::new(sender),
|
||||
recvr,
|
||||
is_init: false,
|
||||
is_setup: false,
|
||||
is_focus_req: AtomicBool::new(false),
|
||||
|
||||
layout: Default::default(),
|
||||
width: None,
|
||||
@@ -92,42 +68,10 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called on `AndroidSuggestEvent::Init` after the View has been added to the main hierarchy
|
||||
/// and is ready to receive commands such as focus.
|
||||
pub fn init(&mut self) {
|
||||
self.is_init = true;
|
||||
|
||||
// Perform any focus requests.
|
||||
let is_focus_req = self.is_focus_req.swap(false, Ordering::SeqCst);
|
||||
if is_focus_req {
|
||||
android::focus(self.composer_id).unwrap();
|
||||
}
|
||||
|
||||
//android::focus(self.composer_id).unwrap();
|
||||
//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);
|
||||
//self.set_selection(2, 7);
|
||||
// Call this after:
|
||||
//self.on_buffer_changed(&mut PropertyAtomicGuard::none()).await;
|
||||
}
|
||||
/// Called on `AndroidSuggestEvent::CreateInputConnect`, which only happens after the View
|
||||
/// is focused for the first time.
|
||||
pub fn setup(&mut self) {
|
||||
assert!(self.is_init);
|
||||
self.is_setup = true;
|
||||
|
||||
assert!(self.composer_id != usize::MAX);
|
||||
t!("Initialized composer [{}]", self.composer_id);
|
||||
}
|
||||
|
||||
pub async fn on_text_prop_changed(&mut self) {
|
||||
// Get modified text property
|
||||
let txt = self.text.get();
|
||||
// Update Android text buffer
|
||||
android::set_text(self.composer_id, &txt);
|
||||
assert_eq!(android::get_editable(self.composer_id).unwrap().buffer, txt);
|
||||
// Update GameTextInput state
|
||||
let state = AndroidTextInputState { text: self.text.get(), select: (0, 0), compose: None };
|
||||
self.input.set_state(&state);
|
||||
// Refresh our layout
|
||||
self.refresh().await;
|
||||
}
|
||||
@@ -136,21 +80,15 @@ impl Editor {
|
||||
self.refresh().await;
|
||||
|
||||
// Update the text attribute
|
||||
let edit = android::get_editable(self.composer_id).unwrap();
|
||||
self.text.set(atom, &edit.buffer);
|
||||
let state = self.input.get_state();
|
||||
self.text.set(atom, &state.text);
|
||||
}
|
||||
|
||||
/// Can only be called after AndroidSuggestEvent::Init.
|
||||
pub fn focus(&self) {
|
||||
// We're not yet ready to receive focus
|
||||
if !self.is_init {
|
||||
self.is_focus_req.store(true, Ordering::SeqCst);
|
||||
return
|
||||
}
|
||||
android::focus(self.composer_id).unwrap();
|
||||
self.input.show_ime();
|
||||
}
|
||||
pub fn unfocus(&self) {
|
||||
android::unfocus(self.composer_id).unwrap();
|
||||
self.input.hide_ime();
|
||||
}
|
||||
|
||||
pub async fn refresh(&mut self) {
|
||||
@@ -159,20 +97,16 @@ impl Editor {
|
||||
let window_scale = self.window_scale.get();
|
||||
let lineheight = self.lineheight.get();
|
||||
|
||||
let edit = android::get_editable(self.composer_id).unwrap();
|
||||
let state = self.input.get_state();
|
||||
|
||||
let mut underlines = vec![];
|
||||
if let Some(compose_start) = edit.compose_start {
|
||||
let compose_end = edit.compose_end.unwrap();
|
||||
|
||||
let compose_start = char16_to_byte_index(&edit.buffer, compose_start).unwrap();
|
||||
let compose_end = char16_to_byte_index(&edit.buffer, compose_end).unwrap();
|
||||
if let Some((compose_start, compose_end)) = state.compose {
|
||||
underlines.push(compose_start..compose_end);
|
||||
}
|
||||
|
||||
let mut txt_ctx = TEXT_CTX.get().await;
|
||||
self.layout = txt_ctx.make_layout(
|
||||
&edit.buffer,
|
||||
&state.text,
|
||||
text_color,
|
||||
font_size,
|
||||
lineheight,
|
||||
@@ -188,12 +122,14 @@ impl Editor {
|
||||
|
||||
pub fn move_to_pos(&self, pos: Point) {
|
||||
let cursor = parley::Cursor::from_point(&self.layout, pos.x, pos.y);
|
||||
|
||||
let edit = android::get_editable(self.composer_id).unwrap();
|
||||
let cursor_idx = cursor.index();
|
||||
let pos = byte_to_char16_index(&edit.buffer, cursor_idx).unwrap();
|
||||
t!(" {cursor_idx} => {pos}");
|
||||
android::set_selection(self.composer_id, pos, pos);
|
||||
t!(" move_to_pos: {cursor_idx}");
|
||||
let state = AndroidTextInputState {
|
||||
text: self.text.get(),
|
||||
select: (cursor_idx, cursor_idx),
|
||||
compose: None,
|
||||
};
|
||||
self.input.set_state(&state);
|
||||
}
|
||||
|
||||
pub async fn select_word_at_point(&mut self, pos: Point) {
|
||||
@@ -205,29 +141,31 @@ impl Editor {
|
||||
|
||||
pub fn get_cursor_pos(&self) -> Point {
|
||||
let lineheight = self.lineheight.get();
|
||||
let edit = android::get_editable(self.composer_id).unwrap();
|
||||
let state = self.input.get_state();
|
||||
|
||||
let cursor_byte_idx = char16_to_byte_index(&edit.buffer, edit.select_start).unwrap();
|
||||
let cursor_idx = state.select.0;
|
||||
|
||||
let cursor = if cursor_byte_idx >= edit.buffer.len() {
|
||||
let cursor = if cursor_idx >= state.text.len() {
|
||||
parley::Cursor::from_byte_index(
|
||||
&self.layout,
|
||||
edit.buffer.len(),
|
||||
state.text.len(),
|
||||
parley::Affinity::Upstream,
|
||||
)
|
||||
} else {
|
||||
parley::Cursor::from_byte_index(
|
||||
&self.layout,
|
||||
cursor_byte_idx,
|
||||
parley::Affinity::Downstream,
|
||||
)
|
||||
parley::Cursor::from_byte_index(&self.layout, cursor_idx, parley::Affinity::Downstream)
|
||||
};
|
||||
let cursor_rect = cursor.geometry(&self.layout, lineheight);
|
||||
Point::new(cursor_rect.x0 as f32, cursor_rect.y0 as f32)
|
||||
}
|
||||
|
||||
pub async fn insert(&mut self, txt: &str, atom: &mut PropertyAtomicGuard) {
|
||||
android::commit_text(self.composer_id, txt);
|
||||
// TODO: need to verify this is correct
|
||||
// Insert text by updating the state
|
||||
let mut current_state = self.input.get_state();
|
||||
current_state.text.push_str(txt);
|
||||
current_state.select = (current_state.text.len(), current_state.text.len());
|
||||
current_state.compose = None;
|
||||
self.input.set_state(¤t_state);
|
||||
self.on_buffer_changed(atom).await;
|
||||
}
|
||||
|
||||
@@ -249,26 +187,21 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn selected_text(&self) -> Option<String> {
|
||||
let edit = android::get_editable(self.composer_id).unwrap();
|
||||
if edit.select_start == edit.select_end {
|
||||
let state = self.input.get_state();
|
||||
if state.select.0 == state.select.1 {
|
||||
return None
|
||||
}
|
||||
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())
|
||||
let (start, end) =
|
||||
(min(state.select.0, state.select.1), max(state.select.0, state.select.1));
|
||||
Some(state.text[start..end].to_string())
|
||||
}
|
||||
pub fn selection(&self, side: isize) -> parley::Selection {
|
||||
assert!(side.abs() == 1);
|
||||
let edit = android::get_editable(self.composer_id).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();
|
||||
//t!("selection() -> ({select_start}, {select_end})");
|
||||
let state = self.input.get_state();
|
||||
|
||||
let (anchor, focus) = match side {
|
||||
-1 => (select_end, select_start),
|
||||
1 => (select_start, select_end),
|
||||
-1 => (state.select.1, state.select.0),
|
||||
1 => (state.select.0, state.select.1),
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
@@ -280,16 +213,16 @@ impl Editor {
|
||||
parley::Selection::new(anchor, focus)
|
||||
}
|
||||
pub async 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();
|
||||
android::set_selection(self.composer_id, select_start, select_end);
|
||||
let state = AndroidTextInputState {
|
||||
text: self.text.get(),
|
||||
select: (select_start, select_end),
|
||||
compose: None,
|
||||
};
|
||||
self.input.set_state(&state);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn buffer(&self) -> String {
|
||||
let edit = android::get_editable(self.composer_id).unwrap();
|
||||
edit.buffer
|
||||
self.input.get_state().text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ use std::{
|
||||
use tracing::instrument;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use crate::android::AndroidSuggestEvent;
|
||||
use crate::android::textinput::AndroidTextInputState;
|
||||
use crate::{
|
||||
gfx::{gfxtag, DrawCall, DrawInstruction, DrawMesh, Point, Rectangle, RenderApi, Vertex},
|
||||
mesh::MeshBuilder,
|
||||
@@ -1296,49 +1296,26 @@ impl BaseEdit {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
async fn handle_android_event(&self, ev: AndroidSuggestEvent) {
|
||||
async fn handle_android_event(&self, state: AndroidTextInputState) {
|
||||
if !self.is_active.get() {
|
||||
return
|
||||
}
|
||||
t!("handle_android_event({ev:?})");
|
||||
t!("handle_android_event({state:?})");
|
||||
|
||||
let atom = &mut self.render_api.make_guard(gfxtag!("BaseEdit::handle_android_event"));
|
||||
match ev {
|
||||
AndroidSuggestEvent::Init => {
|
||||
let mut editor = self.lock_editor().await;
|
||||
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);
|
||||
|
||||
// Debug code if we set text in editor.init()
|
||||
//editor.on_buffer_changed(&mut PropertyAtomicGuard::none()).await;
|
||||
return
|
||||
}
|
||||
AndroidSuggestEvent::CreateInputConnect => {
|
||||
let mut editor = self.lock_editor().await;
|
||||
editor.setup();
|
||||
}
|
||||
// Destructive text edits
|
||||
AndroidSuggestEvent::ComposeRegion { .. } |
|
||||
AndroidSuggestEvent::Compose { .. } |
|
||||
AndroidSuggestEvent::DeleteSurroundingText { .. } => {
|
||||
// Any editing will collapse selections
|
||||
self.finish_select(atom);
|
||||
|
||||
let mut editor = self.lock_editor().await;
|
||||
editor.on_buffer_changed(atom).await;
|
||||
drop(editor);
|
||||
|
||||
self.eval_rect().await;
|
||||
self.behave.apply_cursor_scroll().await;
|
||||
}
|
||||
AndroidSuggestEvent::FinishCompose => {
|
||||
let mut editor = self.lock_editor().await;
|
||||
editor.on_buffer_changed(atom).await;
|
||||
}
|
||||
// Text changed - finish any active selection
|
||||
if state.text != self.text.get() {
|
||||
self.finish_select(atom);
|
||||
}
|
||||
|
||||
let mut editor = self.lock_editor().await;
|
||||
editor.on_buffer_changed(atom).await;
|
||||
|
||||
drop(editor);
|
||||
self.eval_rect().await;
|
||||
self.behave.apply_cursor_scroll().await;
|
||||
|
||||
// Only redraw once we have the parent_rect
|
||||
// Can happen when we receive an Android event before the canvas is ready
|
||||
if self.parent_rect.lock().is_some() {
|
||||
|
||||
Reference in New Issue
Block a user