diff --git a/internal/backends/android-activity/java/SlintAndroidJavaHelper.java b/internal/backends/android-activity/java/SlintAndroidJavaHelper.java index 2267be2c8..44a6902a1 100644 --- a/internal/backends/android-activity/java/SlintAndroidJavaHelper.java +++ b/internal/backends/android-activity/java/SlintAndroidJavaHelper.java @@ -28,13 +28,13 @@ class InputHandle extends ImageView { private SlintInputView mRootView; private int cursorX; private int cursorY; + private int attr; public InputHandle(SlintInputView rootView, int attr) { super(rootView.getContext()); + this.attr = attr; mRootView = rootView; Context ctx = rootView.getContext(); - // this.mInputView = mInputView; - mPopupWindow = new PopupWindow(ctx, null, android.R.attr.textSelectHandleWindowStyle); mPopupWindow.setSplitTouchEnabled(true); mPopupWindow.setClippingEnabled(false); @@ -43,13 +43,6 @@ class InputHandle extends ImageView { mPopupWindow.setWidth(drawable.getIntrinsicWidth()); mPopupWindow.setHeight(drawable.getIntrinsicHeight()); this.setImageDrawable(drawable); - - // mPopupWindow.setBackgroundDrawable(null); - // mPopupWindow.setAnimationStyle(0); - // mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); - // mPopupWindow.setEnterTransition(null); - // mPopupWindow.setExitTransition(null); - mPopupWindow.setContentView(this); } @@ -63,7 +56,9 @@ class InputHandle extends ImageView { } case MotionEvent.ACTION_MOVE: { - SlintAndroidJavaHelper.moveCursorHandle(0, Math.round(ev.getRawX() - mPressedX), + int id = attr == android.R.attr.textSelectHandleLeft ? 1 + : attr == android.R.attr.textSelectHandleRight ? 2 : 0; + SlintAndroidJavaHelper.moveCursorHandle(id, Math.round(ev.getRawX() - mPressedX), Math.round(ev.getRawY() - mPressedY)); break; } @@ -79,7 +74,14 @@ class InputHandle extends ImageView { cursorY = y; y += mPopupWindow.getHeight(); - x -= mPopupWindow.getWidth() / 2; + if (attr == android.R.attr.textSelectHandleLeft) { + x -= 3 * mPopupWindow.getWidth() / 4; + } else if (attr == android.R.attr.textSelectHandleRight) { + x -= mPopupWindow.getWidth() / 4; + } else { + x -= mPopupWindow.getWidth() / 2; + } + mPopupWindow.showAtLocation(mRootView, 0, x, y); mPopupWindow.update(x, y, -1, -1); } @@ -116,7 +118,7 @@ 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); - mHandle.hide(); + setCursorPos(0, 0, 0, 0, 0); if (mInBatch == 0) { update(); } else { @@ -211,22 +213,57 @@ class SlintInputView extends View { } } - private InputHandle mHandle; + private InputHandle mCursorHandle; + private InputHandle mLeftHandle; + private InputHandle mRightHandle; - public void setCursorPos(int rect_x, int rect_y, int rect_w, int rect_h, boolean show) { - if (show) { - if (mHandle == null) { - mHandle = new InputHandle(this, android.R.attr.textSelectHandle); + // num_handles: 0=hidden, 1=cursor handle, 2=selection handles + public void setCursorPos(int left_x, int left_y, int right_x, int right_y, int num_handles) { + if (num_handles == 1) { + if (mLeftHandle != null) { + mLeftHandle.hide(); + } + if (mRightHandle != null) { + mRightHandle.hide(); + } + if (mCursorHandle == null) { + mCursorHandle = new InputHandle(this, android.R.attr.textSelectHandle); + } + mCursorHandle.setPosition(left_x, left_y); + } else if (num_handles == 2) { + if (mLeftHandle == null) { + mLeftHandle = new InputHandle(this, android.R.attr.textSelectHandleLeft); + } + if (mRightHandle == null) { + mRightHandle = new InputHandle(this, android.R.attr.textSelectHandleRight); + } + if (mCursorHandle != null) { + mCursorHandle.hide(); + } + mLeftHandle.setPosition(right_x, right_y); + mRightHandle.setPosition(left_x, left_y); + } else { + if (mCursorHandle != null) { + mCursorHandle.hide(); + } + if (mLeftHandle != null) { + mLeftHandle.hide(); + } + if (mRightHandle != null) { + mRightHandle.hide(); } - mHandle.setPosition(rect_x + rect_w / 2, rect_y + rect_h + 2 * rect_w); - } else if (mHandle != null) { - mHandle.hide(); } } public void setHandleColor(int color) { - if (mHandle != null) { - mHandle.setHandleColor(color); + if (mCursorHandle != null) { + mCursorHandle.setHandleColor(color); + } + if (mLeftHandle != null) { + mLeftHandle.setHandleColor(color); + } + if (mRightHandle != null) { + mRightHandle.setHandleColor(color); } } } @@ -277,7 +314,7 @@ public class SlintAndroidJavaHelper { static public native void moveCursorHandle(int id, int pos_x, int pos_y); 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, boolean show_cursor_handles) { + int cur_x, int cur_y, int anchor_x, int anchor_y, int input_type, boolean show_cursor_handles) { mActivity.runOnUiThread(new Runnable() { @Override @@ -285,7 +322,15 @@ public class SlintAndroidJavaHelper { int selStart = Math.min(cursor_position, anchor_position); int selEnd = Math.max(cursor_position, anchor_position); mInputView.setText(text, selStart, selEnd, preedit_start, preedit_end, input_type); - mInputView.setCursorPos(rect_x, rect_y, rect_w, rect_h, show_cursor_handles); + int num_handles = 0; + if (show_cursor_handles) { + num_handles = cursor_position == anchor_position ? 1 : 2; + } + if (cursor_position < anchor_position) { + mInputView.setCursorPos(anchor_x, anchor_y, cur_x, cur_y, num_handles); + } else { + mInputView.setCursorPos(cur_x, cur_y, anchor_x, anchor_y, num_handles); + } } }); diff --git a/internal/backends/android-activity/javahelper.rs b/internal/backends/android-activity/javahelper.rs index 7797d12df..7d8b1c9a9 100644 --- a/internal/backends/android-activity/javahelper.rs +++ b/internal/backends/android-activity/javahelper.rs @@ -148,9 +148,16 @@ impl JavaHelper { _ => 0 as jint, }; - let cur_origin = data.cursor_rect_origin.to_physical(scale_factor); + let cur_origin = dbg!(data.cursor_rect_origin.to_physical(scale_factor)); + let anchor_origin = dbg!(data.anchor_point.to_physical(scale_factor)); let cur_size = data.cursor_rect_size.to_physical(scale_factor); + // Add 2*cur_size.width to the y position to be a bit under the cursor + let cur_x = cur_origin.x + cur_size.width as i32 / 2; + let cur_y = cur_origin.y + cur_size.height as i32 + 2 * cur_size.width as i32; + let anchor_x = anchor_origin.x; + let anchor_y = anchor_origin.y + 2 * cur_size.width as i32; + env.call_method( helper, "set_imm_data", @@ -161,10 +168,10 @@ impl JavaHelper { 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(cur_origin.x as jint), - JValue::from(cur_origin.y as jint), - JValue::from(cur_size.width as jint), - JValue::from(cur_size.height as jint), + JValue::from(cur_x as jint), + JValue::from(cur_y as jint), + JValue::from(anchor_x as jint), + JValue::from(anchor_y as jint), JValue::from(input_type), JValue::from(show_cursor_handles as jboolean), ], @@ -293,7 +300,7 @@ extern "system" fn Java_SlintAndroidJavaHelper_setDarkMode( extern "system" fn Java_SlintAndroidJavaHelper_moveCursorHandle( _env: JNIEnv, _class: JClass, - _id: jint, + id: jint, pos_x: jint, pos_y: jint, ) { @@ -306,14 +313,44 @@ extern "system" fn Java_SlintAndroidJavaHelper_moveCursorHandle( { if let Some(text_input) = focus_item.downcast::() { let scale_factor = adaptor.window.scale_factor(); - let pos = - euclid::point2(pos_x as f32 / scale_factor, pos_y as f32 / scale_factor) - - focus_item.map_to_window(focus_item.geometry().origin).to_vector(); let adaptor = adaptor.clone() as Rc; + let size = text_input + .as_pin_ref() + .font_request(&adaptor) + .pixel_size + .unwrap_or_default() + .get(); + let pos = + euclid::point2( + pos_x as f32 / scale_factor, + pos_y as f32 / scale_factor - size / 2., + ) - focus_item.map_to_window(focus_item.geometry().origin).to_vector(); let text_pos = text_input.as_pin_ref().byte_offset_for_position(pos, &adaptor); - text_input.anchor_position_byte_offset.set(text_pos as i32); + + let cur_pos = if id == 0 { + text_input.anchor_position_byte_offset.set(text_pos as i32); + text_pos as i32 + } else { + let current_cursor = text_input.as_pin_ref().cursor_position_byte_offset(); + let current_anchor = text_input.as_pin_ref().anchor_position_byte_offset(); + if (id == 1 && current_anchor < current_cursor) + || (id == 2 && current_anchor > current_cursor) + { + if current_cursor == text_pos as i32 { + return; + } + text_input.anchor_position_byte_offset.set(text_pos as i32); + current_cursor + } else { + if current_anchor == text_pos as i32 { + return; + } + text_pos as i32 + } + }; + text_input.as_pin_ref().set_cursor_position( - text_pos as i32, + cur_pos, true, i_slint_core::items::TextChangeNotify::TriggerCallbacks, &adaptor, diff --git a/internal/core/items/text.rs b/internal/core/items/text.rs index 85cf2370b..db5d2aa63 100644 --- a/internal/core/items/text.rs +++ b/internal/core/items/text.rs @@ -1126,10 +1126,15 @@ impl TextInput { let anchor_position = self.anchor_position(&text); let cursor_relative = self.cursor_rect_for_byte_offset(cursor_position, window_adapter); let geometry = self_rc.geometry(); - let cursor_rect_origin = crate::api::LogicalPosition::from_euclid( - self_rc.map_to_window(cursor_relative.origin + geometry.origin.to_vector()), - ); + let origin = self_rc.map_to_window(geometry.origin).to_vector(); + let cursor_rect_origin = + crate::api::LogicalPosition::from_euclid(cursor_relative.origin + origin); let cursor_rect_size = crate::api::LogicalSize::from_euclid(cursor_relative.size); + let anchor_point = crate::api::LogicalPosition::from_euclid( + self.cursor_rect_for_byte_offset(anchor_position, window_adapter).origin + + origin + + cursor_relative.size, + ); InputMethodProperties { text, @@ -1139,6 +1144,7 @@ impl TextInput { preedit_offset: cursor_position, cursor_rect_origin, cursor_rect_size, + anchor_point, input_type: self.input_type(), } } diff --git a/internal/core/window.rs b/internal/core/window.rs index 7488cb1a9..3de16656c 100644 --- a/internal/core/window.rs +++ b/internal/core/window.rs @@ -226,6 +226,8 @@ pub struct InputMethodProperties { pub cursor_rect_origin: LogicalPosition, /// The size of the cursor rectangle. pub cursor_rect_size: crate::api::LogicalSize, + /// The position of the anchor (bottom). Only meaningful if anchor_position is Some + pub anchor_point: LogicalPosition, /// The type of input for the text edit. pub input_type: InputType, }