mirror of https://github.com/termux/termux-app
346 lines
11 KiB
Java
346 lines
11 KiB
Java
package com.termux.view.textselection;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Rect;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.SystemClock;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.ViewParent;
|
|
import android.view.WindowManager;
|
|
import android.widget.PopupWindow;
|
|
|
|
import com.termux.view.R;
|
|
import com.termux.view.TerminalView;
|
|
|
|
@SuppressLint("ViewConstructor")
|
|
public class TextSelectionHandleView extends View {
|
|
private final TerminalView terminalView;
|
|
private PopupWindow mHandle;
|
|
private final CursorController mCursorController;
|
|
|
|
private final Drawable mHandleLeftDrawable;
|
|
private final Drawable mHandleRightDrawable;
|
|
private Drawable mHandleDrawable;
|
|
|
|
private boolean mIsDragging;
|
|
|
|
final int[] mTempCoords = new int[2];
|
|
Rect mTempRect;
|
|
|
|
private int mPointX;
|
|
private int mPointY;
|
|
private float mTouchToWindowOffsetX;
|
|
private float mTouchToWindowOffsetY;
|
|
private float mHotspotX;
|
|
private float mHotspotY;
|
|
private float mTouchOffsetY;
|
|
private int mLastParentX;
|
|
private int mLastParentY;
|
|
|
|
private int mHandleHeight;
|
|
private int mHandleWidth;
|
|
|
|
private final int mInitialOrientation;
|
|
private int mOrientation;
|
|
|
|
public static final int LEFT = 0;
|
|
public static final int RIGHT = 2;
|
|
|
|
private long mLastTime;
|
|
|
|
public TextSelectionHandleView(TerminalView terminalView, CursorController cursorController, int initialOrientation) {
|
|
super(terminalView.getContext());
|
|
this.terminalView = terminalView;
|
|
mCursorController = cursorController;
|
|
mInitialOrientation = initialOrientation;
|
|
|
|
mHandleLeftDrawable = getContext().getDrawable(R.drawable.text_select_handle_left_material);
|
|
mHandleRightDrawable = getContext().getDrawable(R.drawable.text_select_handle_right_material);
|
|
|
|
setOrientation(mInitialOrientation);
|
|
}
|
|
|
|
private void initHandle() {
|
|
mHandle = new PopupWindow(terminalView.getContext(), null,
|
|
android.R.attr.textSelectHandleWindowStyle);
|
|
mHandle.setSplitTouchEnabled(true);
|
|
mHandle.setClippingEnabled(false);
|
|
mHandle.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
|
|
mHandle.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
mHandle.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
mHandle.setBackgroundDrawable(null);
|
|
mHandle.setAnimationStyle(0);
|
|
mHandle.setEnterTransition(null);
|
|
mHandle.setExitTransition(null);
|
|
mHandle.setContentView(this);
|
|
}
|
|
|
|
public void setOrientation(int orientation) {
|
|
mOrientation = orientation;
|
|
int handleWidth = 0;
|
|
switch (orientation) {
|
|
case LEFT: {
|
|
mHandleDrawable = mHandleLeftDrawable;
|
|
handleWidth = mHandleDrawable.getIntrinsicWidth();
|
|
mHotspotX = (handleWidth * 3) / (float) 4;
|
|
break;
|
|
}
|
|
|
|
case RIGHT: {
|
|
mHandleDrawable = mHandleRightDrawable;
|
|
handleWidth = mHandleDrawable.getIntrinsicWidth();
|
|
mHotspotX = handleWidth / (float) 4;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mHandleHeight = mHandleDrawable.getIntrinsicHeight();
|
|
|
|
mHandleWidth = handleWidth;
|
|
mTouchOffsetY = -mHandleHeight * 0.3f;
|
|
mHotspotY = 0;
|
|
invalidate();
|
|
}
|
|
|
|
public void show() {
|
|
if (!isPositionVisible()) {
|
|
hide();
|
|
return;
|
|
}
|
|
|
|
// We remove handle from its parent first otherwise the following exception may be thrown
|
|
// java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
|
|
removeFromParent();
|
|
|
|
initHandle(); // init the handle
|
|
invalidate(); // invalidate to make sure onDraw is called
|
|
|
|
final int[] coords = mTempCoords;
|
|
terminalView.getLocationInWindow(coords);
|
|
coords[0] += mPointX;
|
|
coords[1] += mPointY;
|
|
|
|
if (mHandle != null)
|
|
mHandle.showAtLocation(terminalView, 0, coords[0], coords[1]);
|
|
}
|
|
|
|
public void hide() {
|
|
mIsDragging = false;
|
|
|
|
if (mHandle != null) {
|
|
mHandle.dismiss();
|
|
|
|
// We remove handle from its parent, otherwise it may still be shown in some cases even after the dismiss call
|
|
removeFromParent();
|
|
mHandle = null; // garbage collect the handle
|
|
}
|
|
invalidate();
|
|
}
|
|
|
|
public void removeFromParent() {
|
|
if (!isParentNull()) {
|
|
((ViewGroup)this.getParent()).removeView(this);
|
|
}
|
|
}
|
|
|
|
public void positionAtCursor(final int cx, final int cy, boolean forceOrientationCheck) {
|
|
int x = terminalView.getPointX(cx);
|
|
int y = terminalView.getPointY(cy + 1);
|
|
moveTo(x, y, forceOrientationCheck);
|
|
}
|
|
|
|
private void moveTo(int x, int y, boolean forceOrientationCheck) {
|
|
float oldHotspotX = mHotspotX;
|
|
checkChangedOrientation(x, forceOrientationCheck);
|
|
mPointX = (int) (x - (isShowing() ? oldHotspotX : mHotspotX));
|
|
mPointY = y;
|
|
|
|
if (isPositionVisible()) {
|
|
int[] coords = null;
|
|
|
|
if (isShowing()) {
|
|
coords = mTempCoords;
|
|
terminalView.getLocationInWindow(coords);
|
|
int x1 = coords[0] + mPointX;
|
|
int y1 = coords[1] + mPointY;
|
|
if (mHandle != null)
|
|
mHandle.update(x1, y1, getWidth(), getHeight());
|
|
} else {
|
|
show();
|
|
}
|
|
|
|
if (mIsDragging) {
|
|
if (coords == null) {
|
|
coords = mTempCoords;
|
|
terminalView.getLocationInWindow(coords);
|
|
}
|
|
if (coords[0] != mLastParentX || coords[1] != mLastParentY) {
|
|
mTouchToWindowOffsetX += coords[0] - mLastParentX;
|
|
mTouchToWindowOffsetY += coords[1] - mLastParentY;
|
|
mLastParentX = coords[0];
|
|
mLastParentY = coords[1];
|
|
}
|
|
}
|
|
} else {
|
|
hide();
|
|
}
|
|
}
|
|
|
|
public void changeOrientation(int orientation) {
|
|
if (mOrientation != orientation) {
|
|
setOrientation(orientation);
|
|
}
|
|
}
|
|
|
|
private void checkChangedOrientation(int posX, boolean force) {
|
|
if (!mIsDragging && !force) {
|
|
return;
|
|
}
|
|
long millis = SystemClock.currentThreadTimeMillis();
|
|
if (millis - mLastTime < 50 && !force) {
|
|
return;
|
|
}
|
|
mLastTime = millis;
|
|
|
|
final TerminalView hostView = terminalView;
|
|
final int left = hostView.getLeft();
|
|
final int right = hostView.getWidth();
|
|
final int top = hostView.getTop();
|
|
final int bottom = hostView.getHeight();
|
|
|
|
if (mTempRect == null) {
|
|
mTempRect = new Rect();
|
|
}
|
|
final Rect clip = mTempRect;
|
|
clip.left = left + terminalView.getPaddingLeft();
|
|
clip.top = top + terminalView.getPaddingTop();
|
|
clip.right = right - terminalView.getPaddingRight();
|
|
clip.bottom = bottom - terminalView.getPaddingBottom();
|
|
|
|
final ViewParent parent = hostView.getParent();
|
|
if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
|
|
return;
|
|
}
|
|
|
|
if (posX - mHandleWidth < clip.left) {
|
|
changeOrientation(RIGHT);
|
|
} else if (posX + mHandleWidth > clip.right) {
|
|
changeOrientation(LEFT);
|
|
} else {
|
|
changeOrientation(mInitialOrientation);
|
|
}
|
|
}
|
|
|
|
private boolean isPositionVisible() {
|
|
// Always show a dragging handle.
|
|
if (mIsDragging) {
|
|
return true;
|
|
}
|
|
|
|
final TerminalView hostView = terminalView;
|
|
final int left = 0;
|
|
final int right = hostView.getWidth();
|
|
final int top = 0;
|
|
final int bottom = hostView.getHeight();
|
|
|
|
if (mTempRect == null) {
|
|
mTempRect = new Rect();
|
|
}
|
|
final Rect clip = mTempRect;
|
|
clip.left = left + terminalView.getPaddingLeft();
|
|
clip.top = top + terminalView.getPaddingTop();
|
|
clip.right = right - terminalView.getPaddingRight();
|
|
clip.bottom = bottom - terminalView.getPaddingBottom();
|
|
|
|
final ViewParent parent = hostView.getParent();
|
|
if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
|
|
return false;
|
|
}
|
|
|
|
final int[] coords = mTempCoords;
|
|
hostView.getLocationInWindow(coords);
|
|
final int posX = coords[0] + mPointX + (int) mHotspotX;
|
|
final int posY = coords[1] + mPointY + (int) mHotspotY;
|
|
|
|
return posX >= clip.left && posX <= clip.right &&
|
|
posY >= clip.top && posY <= clip.bottom;
|
|
}
|
|
|
|
@Override
|
|
public void onDraw(Canvas c) {
|
|
final int width = mHandleDrawable.getIntrinsicWidth();
|
|
int height = mHandleDrawable.getIntrinsicHeight();
|
|
mHandleDrawable.setBounds(0, 0, width, height);
|
|
mHandleDrawable.draw(c);
|
|
}
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
terminalView.updateFloatingToolbarVisibility(event);
|
|
switch (event.getActionMasked()) {
|
|
case MotionEvent.ACTION_DOWN: {
|
|
final float rawX = event.getRawX();
|
|
final float rawY = event.getRawY();
|
|
mTouchToWindowOffsetX = rawX - mPointX;
|
|
mTouchToWindowOffsetY = rawY - mPointY;
|
|
final int[] coords = mTempCoords;
|
|
terminalView.getLocationInWindow(coords);
|
|
mLastParentX = coords[0];
|
|
mLastParentY = coords[1];
|
|
mIsDragging = true;
|
|
break;
|
|
}
|
|
|
|
case MotionEvent.ACTION_MOVE: {
|
|
final float rawX = event.getRawX();
|
|
final float rawY = event.getRawY();
|
|
|
|
final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
|
|
final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY + mTouchOffsetY;
|
|
|
|
mCursorController.updatePosition(this, Math.round(newPosX), Math.round(newPosY));
|
|
break;
|
|
}
|
|
|
|
case MotionEvent.ACTION_UP:
|
|
case MotionEvent.ACTION_CANCEL:
|
|
mIsDragging = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
setMeasuredDimension(mHandleDrawable.getIntrinsicWidth(),
|
|
mHandleDrawable.getIntrinsicHeight());
|
|
}
|
|
|
|
public int getHandleHeight() {
|
|
return mHandleHeight;
|
|
}
|
|
|
|
public int getHandleWidth() {
|
|
return mHandleWidth;
|
|
}
|
|
|
|
public boolean isShowing() {
|
|
if (mHandle != null)
|
|
return mHandle.isShowing();
|
|
else
|
|
return false;
|
|
}
|
|
|
|
public boolean isParentNull() {
|
|
return this.getParent() == null;
|
|
}
|
|
|
|
public boolean isDragging() {
|
|
return mIsDragging;
|
|
}
|
|
|
|
}
|