Android: support for preedit

(Tested with the typewise keyboard)
This commit is contained in:
Olivier Goffart 2024-02-27 16:23:45 +01:00
parent 1fb162936e
commit c4e0f4a265
2 changed files with 85 additions and 42 deletions

View file

@ -19,14 +19,14 @@ class SlintInputView extends View {
private String mText = "";
private int mCursorPosition = 0;
private int mAnchorPosition = 0;
private String mPreedit = "";
private int mPreeditOffset;
private int mPreeditStart = 0;
private int mPreeditEnd = 0;
private int mInputType = EditorInfo.TYPE_CLASS_TEXT;
private SpannableStringBuilder mEditable;
private int mInBatch = 0;
private boolean mPending = false;
private SlintEditable mEditable;
public class SlintEditable extends SpannableStringBuilder {
// private SlintInputView mInputView;
public SlintEditable() {
super(mText);
}
@ -34,18 +34,23 @@ class SlintInputView extends View {
@Override
public SpannableStringBuilder replace(int start, int end, CharSequence tb, int tbstart, int tbend) {
super.replace(start, end, tb, tbstart, tbend);
mText = toString();
mCursorPosition = Selection.getSelectionStart(this);
mAnchorPosition = Selection.getSelectionEnd(this);
SlintAndroidJavaHelper.updateText(mText, mCursorPosition, mAnchorPosition, "", mCursorPosition);
System.out.println("replace '" + toString() + "' mInBatch=" + mInBatch);
if (mInBatch == 0) {
update();
} else {
mPending = true;
}
return this;
}
@Override
public void setSpan(Object what, int start, int end, int flags) {
super.setSpan(what, start, end, flags);
public void update() {
mPending = false;
mText = toString();
mCursorPosition = Selection.getSelectionStart(this);
mAnchorPosition = Selection.getSelectionEnd(this);
mPreeditStart = BaseInputConnection.getComposingSpanStart(this);
mPreeditEnd = BaseInputConnection.getComposingSpanEnd(this);
SlintAndroidJavaHelper.updateText(mText, mCursorPosition, mAnchorPosition, mPreeditStart, mPreeditEnd);
}
}
@ -72,18 +77,33 @@ class SlintInputView extends View {
public Editable getEditable() {
return mEditable;
}
@Override
public boolean beginBatchEdit() {
mInBatch += 1;
return super.beginBatchEdit();
}
@Override
public boolean endBatchEdit() {
mInBatch -= 1;
if (mInBatch == 0 && mPending) {
mEditable.update();
}
return super.endBatchEdit();
}
};
}
public void setText(String text, int cursorPosition, int anchorPosition, String preedit, int preeditOffset,
public void setText(String text, int cursorPosition, int anchorPosition, int preeditStart, int preeditEnd,
int inputType) {
boolean restart = mInputType != inputType || !mText.equals(text) || mCursorPosition != cursorPosition
|| mAnchorPosition != anchorPosition;
mText = text;
mCursorPosition = cursorPosition;
mAnchorPosition = anchorPosition;
mPreedit = preedit;
mPreeditOffset = preeditOffset;
mPreeditStart = preeditStart;
mPreeditEnd = preeditEnd;
mInputType = inputType;
if (restart) {
@ -148,12 +168,12 @@ public class SlintAndroidJavaHelper {
});
}
static public native void updateText(String text, int cursorPosition, int anchorPosition, String preedit,
static public native void updateText(String text, int cursorPosition, int anchorPosition, int preeditStart,
int preeditOffset);
static public native void setDarkMode(boolean dark);
public void set_imm_data(String text, int cursor_position, int anchor_position, String preedit, int preedit_offset,
public void set_imm_data(String text, int cursor_position, int anchor_position, int preedit_start, int preedit_end,
int rect_x, int rect_y, int rect_w, int rect_h, int input_type) {
mActivity.runOnUiThread(new Runnable() {
@ -164,7 +184,7 @@ public class SlintAndroidJavaHelper {
mInputView.setLayoutParams(layoutParams);
int selStart = Math.min(cursor_position, anchor_position);
int selEnd = Math.max(cursor_position, anchor_position);
mInputView.setText(text, selStart, selEnd, preedit, preedit_offset, input_type);
mInputView.setText(text, selStart, selEnd, preedit_start, preedit_end, input_type);
}
});
}

View file

@ -52,7 +52,7 @@ fn load_java_helper(app: &AndroidApp) -> Result<jni::objects::GlobalRef, jni::er
let methods = [
jni::NativeMethod {
name: "updateText".into(),
sig: "(Ljava/lang/String;IILjava/lang/String;I)V".into(),
sig: "(Ljava/lang/String;IIII)V".into(),
fn_ptr: Java_SlintAndroidJavaHelper_updateText as *mut _,
},
jni::NativeMethod {
@ -105,9 +105,22 @@ impl JavaHelper {
data: &i_slint_core::window::InputMethodProperties,
) -> Result<(), jni::errors::Error> {
self.with_jni_env(|env, helper| {
let text = &env.new_string(data.text.as_str())?;
let preedit_text = env.new_string(data.preedit_text.as_str())?;
let to_utf16 = |x| convert_utf8_index_to_utf16(&data.text, x as usize);
let mut text = data.text.to_string();
let mut cursor_position = data.cursor_position;
let mut anchor_position = data.anchor_position.unwrap_or(data.cursor_position);
if !data.preedit_text.is_empty() {
text.insert_str(data.preedit_offset, data.preedit_text.as_str());
if cursor_position >= data.preedit_offset {
cursor_position += data.preedit_text.len()
}
if anchor_position >= data.preedit_offset {
anchor_position += data.preedit_text.len()
}
}
let to_utf16 = |x| convert_utf8_index_to_utf16(&text, x as usize);
let text = &env.new_string(text.as_str())?;
let class_it = env.find_class("android/text/InputType")?;
let input_type = match data.input_type {
@ -128,15 +141,13 @@ impl JavaHelper {
env.call_method(
helper,
"set_imm_data",
"(Ljava/lang/String;IILjava/lang/String;IIIIII)V",
"(Ljava/lang/String;IIIIIIIII)V",
&[
JValue::Object(&text),
JValue::from(to_utf16(data.cursor_position) as jint),
JValue::from(
to_utf16(data.anchor_position.unwrap_or(data.cursor_position)) as jint
),
JValue::Object(&preedit_text),
JValue::from(to_utf16(cursor_position) as jint),
JValue::from(to_utf16(anchor_position) as jint),
JValue::from(to_utf16(data.preedit_offset) as jint),
JValue::from(to_utf16(data.preedit_offset + data.preedit_text.len()) as jint),
JValue::from(data.cursor_rect_origin.x as jint),
JValue::from(data.cursor_rect_origin.y as jint),
JValue::from(data.cursor_rect_size.width as jint),
@ -175,8 +186,8 @@ extern "system" fn Java_SlintAndroidJavaHelper_updateText(
text: JString,
cursor_position: jint,
anchor_position: jint,
preedit: JString,
preedit_offset: jint,
preedit_start: jint,
preedit_end: jint,
) {
fn make_shared_string(env: &mut JNIEnv, string: &JString) -> Option<SharedString> {
let java_str = env.get_string(&string).ok()?;
@ -184,24 +195,36 @@ extern "system" fn Java_SlintAndroidJavaHelper_updateText(
Some(SharedString::from(decoded.as_ref()))
}
let Some(text) = make_shared_string(&mut env, &text) else { return };
let Some(preedit) = make_shared_string(&mut env, &preedit) else { return };
let cursor_position = convert_utf16_index_to_utf8(&text, cursor_position as usize);
let anchor_position = convert_utf16_index_to_utf8(&text, anchor_position as usize);
let preedit_offset = convert_utf16_index_to_utf8(&text, preedit_offset as usize) as i32;
let preedit_start = convert_utf16_index_to_utf8(&text, preedit_start as usize);
let preedit_end = convert_utf16_index_to_utf8(&text, preedit_end as usize);
i_slint_core::api::invoke_from_event_loop(move || {
if let Some(adaptor) = CURRENT_WINDOW.with_borrow(|x| x.upgrade()) {
let runtime_window = i_slint_core::window::WindowInner::from_pub(&adaptor.window);
let event = i_slint_core::input::KeyEvent {
let event = if preedit_start != preedit_end {
let adjust = |pos| if pos <= preedit_start { pos } else if pos >= preedit_end { pos - preedit_end + preedit_start } else { preedit_start } as i32;
i_slint_core::input::KeyEvent {
event_type: i_slint_core::input::KeyEventType::UpdateComposition,
text: i_slint_core::format!( "{}{}", &text[..preedit_start], &text[preedit_end..]),
preedit_text: text[preedit_start..preedit_end].into(),
preedit_selection: Some(0..(preedit_end - preedit_start) as i32),
replacement_range: Some(i32::MIN..i32::MAX),
cursor_position: Some(adjust(cursor_position)),
anchor_position: Some(adjust(anchor_position)),
..Default::default()
}
} else {
i_slint_core::input::KeyEvent {
event_type: i_slint_core::input::KeyEventType::CommitComposition,
text,
replacement_range: Some(i32::MIN..i32::MAX),
cursor_position: Some(cursor_position as _),
anchor_position: Some(anchor_position as _),
preedit_selection: (!preedit.is_empty())
.then(|| preedit_offset..(preedit_offset + preedit.len() as i32)),
preedit_text: preedit,
..Default::default()
}
};
runtime_window.process_key_input(event);
}