mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-31 07:37:24 +00:00
227 lines
8.7 KiB
Rust
227 lines
8.7 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
|
|
|
use super::*;
|
|
use i_slint_core::api::{PhysicalPosition, PhysicalSize};
|
|
use i_slint_core::SharedString;
|
|
use jni::objects::{JClass, JString};
|
|
use jni::sys::{jboolean, jint};
|
|
use jni::JNIEnv;
|
|
|
|
#[track_caller]
|
|
pub fn print_jni_error(app: &AndroidApp, e: jni::errors::Error) -> ! {
|
|
let vm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as *mut _) }.unwrap();
|
|
let env = vm.attach_current_thread().unwrap();
|
|
let _ = env.exception_describe();
|
|
panic!("JNI error: {e:?}")
|
|
}
|
|
|
|
pub struct JavaHelper(jni::objects::GlobalRef);
|
|
|
|
fn load_java_helper(app: &AndroidApp) -> Result<jni::objects::GlobalRef, jni::errors::Error> {
|
|
use jni::objects::{JObject, JValue};
|
|
// Safety: as documented in android-activity to obtain a jni::JavaVM
|
|
let vm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as *mut _) }?;
|
|
let native_activity = unsafe { JObject::from_raw(app.activity_as_ptr() as *mut _) };
|
|
|
|
let mut env = vm.attach_current_thread()?;
|
|
|
|
let dex_data = include_bytes!(concat!(env!("OUT_DIR"), "/classes.dex"));
|
|
|
|
// Safety: dex_data is 'static and the InMemoryDexClassLoader will not mutate it it
|
|
let dex_buffer =
|
|
unsafe { env.new_direct_byte_buffer(dex_data.as_ptr() as *mut _, dex_data.len()).unwrap() };
|
|
|
|
let dex_loader = env.new_object(
|
|
"dalvik/system/InMemoryDexClassLoader",
|
|
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V",
|
|
&[JValue::Object(&dex_buffer), JValue::Object(&JObject::null())],
|
|
)?;
|
|
|
|
let class_name = env.new_string("SlintAndroidJavaHelper")?;
|
|
let helper_class = env
|
|
.call_method(
|
|
dex_loader,
|
|
"findClass",
|
|
"(Ljava/lang/String;)Ljava/lang/Class;",
|
|
&[JValue::Object(&class_name)],
|
|
)?
|
|
.l()?;
|
|
let helper_class: JClass = helper_class.into();
|
|
|
|
let methods = [
|
|
jni::NativeMethod {
|
|
name: "updateText".into(),
|
|
sig: "(Ljava/lang/String;IILjava/lang/String;I)V".into(),
|
|
fn_ptr: Java_SlintAndroidJavaHelper_updateText as *mut _,
|
|
},
|
|
jni::NativeMethod {
|
|
name: "setDarkMode".into(),
|
|
sig: "(Z)V".into(),
|
|
fn_ptr: Java_SlintAndroidJavaHelper_setDarkMode as *mut _,
|
|
},
|
|
];
|
|
env.register_native_methods(&helper_class, &methods)?;
|
|
|
|
let helper_instance = env.new_object(
|
|
helper_class,
|
|
"(Landroid/app/Activity;)V",
|
|
&[JValue::Object(&native_activity)],
|
|
)?;
|
|
Ok(env.new_global_ref(&helper_instance)?)
|
|
}
|
|
|
|
impl JavaHelper {
|
|
pub fn new(_app: &AndroidApp) -> Result<Self, jni::errors::Error> {
|
|
Ok(Self(load_java_helper(_app)?))
|
|
}
|
|
|
|
/// Unfortunately, the way that the android-activity crate uses to show or hide the virtual keyboard doesn't
|
|
/// work with native-activity. So do it manually with JNI
|
|
pub fn show_or_hide_soft_input(
|
|
&self,
|
|
app: &AndroidApp,
|
|
show: bool,
|
|
) -> Result<(), jni::errors::Error> {
|
|
// Safety: as documented in android-activity to obtain a jni::JavaVM
|
|
let vm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as *mut _) }?;
|
|
let mut env = vm.attach_current_thread()?;
|
|
let helper = self.0.as_obj();
|
|
if show {
|
|
env.call_method(helper, "show_keyboard", "()V", &[])?;
|
|
} else {
|
|
env.call_method(helper, "hide_keyboard", "()V", &[])?;
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
pub fn set_imm_data(
|
|
&self,
|
|
app: &AndroidApp,
|
|
data: &i_slint_core::window::InputMethodProperties,
|
|
) -> Result<(), jni::errors::Error> {
|
|
use jni::objects::JValue;
|
|
|
|
// Safety: as documented in android-activity to obtain a jni::JavaVM
|
|
let vm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as *mut _) }?;
|
|
let mut env = vm.attach_current_thread()?;
|
|
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);
|
|
env.call_method(
|
|
self.0.as_obj(),
|
|
"set_imm_data",
|
|
"(Ljava/lang/String;IILjava/lang/String;IIIIII)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(data.preedit_offset) 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),
|
|
JValue::from(data.cursor_rect_size.height as jint),
|
|
JValue::from(data.input_type as jint),
|
|
],
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn dark_color_scheme(&self, app: &AndroidApp) -> Result<bool, jni::errors::Error> {
|
|
// Safety: as documented in android-activity to obtain a jni::JavaVM
|
|
let vm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as *mut _) }?;
|
|
let mut env = vm.attach_current_thread()?;
|
|
let helper = self.0.as_obj();
|
|
Ok(env.call_method(helper, "dark_color_scheme", "()Z", &[])?.z()?)
|
|
}
|
|
|
|
pub fn get_view_rect(
|
|
&self,
|
|
app: &AndroidApp,
|
|
) -> Result<(PhysicalPosition, PhysicalSize), jni::errors::Error> {
|
|
// Safety: as documented in android-activity to obtain a jni::JavaVM
|
|
let vm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as *mut _) }?;
|
|
let mut env = vm.attach_current_thread()?;
|
|
let helper = self.0.as_obj();
|
|
let rect =
|
|
env.call_method(helper, "get_view_rect", "()Landroid/graphics/Rect;", &[])?.l()?;
|
|
let x = env.get_field(&rect, "left", "I")?.i()?;
|
|
let y = env.get_field(&rect, "top", "I")?.i()?;
|
|
let width = env.get_field(&rect, "right", "I")?.i()? - x;
|
|
let height = env.get_field(&rect, "bottom", "I")?.i()? - y;
|
|
Ok((PhysicalPosition::new(x as _, y as _), PhysicalSize::new(width as _, height as _)))
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
extern "system" fn Java_SlintAndroidJavaHelper_updateText(
|
|
mut env: JNIEnv,
|
|
_class: JClass,
|
|
text: JString,
|
|
cursor_position: jint,
|
|
anchor_position: jint,
|
|
preedit: JString,
|
|
preedit_offset: jint,
|
|
) {
|
|
fn make_shared_string(env: &mut JNIEnv, string: &JString) -> Option<SharedString> {
|
|
let java_str = env.get_string(&string).ok()?;
|
|
let decoded: std::borrow::Cow<str> = (&java_str).into();
|
|
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;
|
|
|
|
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 {
|
|
event_type: i_slint_core::input::KeyEventType::UpdateComposition,
|
|
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);
|
|
}
|
|
})
|
|
.unwrap()
|
|
}
|
|
|
|
fn convert_utf16_index_to_utf8(in_str: &str, utf16_index: usize) -> usize {
|
|
let mut utf16_counter = 0;
|
|
|
|
for (utf8_index, c) in in_str.char_indices() {
|
|
if utf16_counter >= utf16_index {
|
|
return utf8_index;
|
|
}
|
|
utf16_counter += c.len_utf16();
|
|
}
|
|
in_str.len()
|
|
}
|
|
|
|
fn convert_utf8_index_to_utf16(in_str: &str, utf8_index: usize) -> usize {
|
|
in_str[..utf8_index].encode_utf16().count()
|
|
}
|
|
|
|
#[no_mangle]
|
|
extern "system" fn Java_SlintAndroidJavaHelper_setDarkMode(
|
|
_env: JNIEnv,
|
|
_class: JClass,
|
|
dark: jboolean,
|
|
) {
|
|
i_slint_core::api::invoke_from_event_loop(move || {
|
|
if let Some(w) = CURRENT_WINDOW.with_borrow(|x| x.upgrade()) {
|
|
w.dark_color_scheme.as_ref().set(dark == jni::sys::JNI_TRUE);
|
|
}
|
|
})
|
|
.unwrap()
|
|
}
|