diff --git a/app_pojavlauncher/src/main/AndroidManifest.xml b/app_pojavlauncher/src/main/AndroidManifest.xml index a25fb0d92e..9af22d294f 100644 --- a/app_pojavlauncher/src/main/AndroidManifest.xml +++ b/app_pojavlauncher/src/main/AndroidManifest.xml @@ -110,6 +110,7 @@ android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|keyboard|navigation|uiMode" android:launchMode="singleTop" android:process=":game" + android:windowSoftInputMode="adjustResize" android:screenOrientation="sensorLandscape" /> runningAnimations) { + imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; + refreshImeTranslation(); + return insets; + } + + @Override + public void onEnd(@NonNull WindowInsetsAnimationCompat animation) { + if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { + hasOngoingImeAnimation = false; + } + } + }); + ViewCompat.setOnApplyWindowInsetsListener(contentFrame, (v, insets) -> { + // Only refresh translation if IME change insets itself, not the animation + if (!hasOngoingImeAnimation) { + imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; + refreshImeTranslation(); + } + return insets; + }); + // Set the activity for the executor. Must do this here, or else Tools.showErrorRemote() may not // execute the correct method ContextExecutor.setActivity(this); @@ -209,7 +259,7 @@ protected void initLayout(int resId) { GLOBAL_CLIPBOARD = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); touchCharInput.setCharacterSender(new LwjglCharSender()); - touchControllerInputView.setSize(minecraftGLView.getWidth(), minecraftGLView.getHeight()); + touchControllerInputView.setInputAreaRectListener(this); if(minecraftProfile.pojavRendererName != null) { Log.i("RdrDebug","__P_renderer="+minecraftProfile.pojavRendererName); @@ -260,6 +310,9 @@ protected void initLayout(int resId) { touchpad.post(() -> touchpad.switchState()); } + // At this time, correct size is known + touchControllerInputView.setSize(minecraftGLView.getWidth(), minecraftGLView.getHeight()); + runCraft(finalVersion, mVersionInfo); }catch (Throwable e){ Tools.showErrorRemote(e); @@ -314,6 +367,7 @@ private void bindValues(){ touchControllerInputView = findViewById(R.id.touch_controller_input); mDrawerPullButton = findViewById(R.id.drawer_button); mHotbarView = findViewById(R.id.hotbar_view); + contentFrame = findViewById(R.id.content_frame); } @Override @@ -509,6 +563,15 @@ public void onResolutionChanged() { mHotbarView.onResolutionChanged(); } + @Override + public void onKeyboardPanningChanged() { + if (LauncherPreferences.PREF_KEYBOARD_PANNING) { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + } else { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); + } + } + @Override public void onGyroStateChanged() { mGyroControl.updateOrientation(); @@ -701,4 +764,33 @@ public void onTrimMemory(int level) { public void onBackPressed() { super.onBackPressed(); } + + @Override + public void updateInputAreaRect(@Nullable RectF rect) { + inputAreaRect = rect; + refreshImeTranslation(); + } + + private void refreshImeTranslation() { + if (imeHeight == 0) { + // Early exit + contentFrame.setTranslationY(0); + return; + } + + int inputAreaBottom; + if (inputAreaRect != null) { + inputAreaBottom = (int) inputAreaRect.bottom; + } else if (LauncherPreferences.PREF_KEYBOARD_PANNING) { + inputAreaBottom = contentFrame.getHeight(); + } else { + contentFrame.setTranslationY(0); + return; + } + + int bottomDistance = contentFrame.getHeight() - inputAreaBottom; + int bottomPadding = Math.max(imeHeight - bottomDistance, 0); + + contentFrame.setTranslationY(-bottomPadding); + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java index 78ca9e7109..f9d48fd2c4 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/Touchpad.java @@ -123,9 +123,11 @@ public boolean getDisplayState() { @Override public void applyMotionVector(float x, float y) { - mMouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mMouseX + x * LauncherPreferences.PREF_MOUSESPEED)); - mMouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mMouseY + y * LauncherPreferences.PREF_MOUSESPEED)); - updateMousePosition(); + if (mDisplayState) { // Make sure no motion leaks through when disabling a moving cursor + mMouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mMouseX + x * LauncherPreferences.PREF_MOUSESPEED)); + mMouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mMouseY + y * LauncherPreferences.PREF_MOUSESPEED)); + updateMousePosition(); + } } @Override diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java index 543a23695e..057af91334 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java @@ -75,6 +75,7 @@ public class LauncherPreferences { public static int PREF_TOUCHCONTROLLER_VIBRATE_LENGTH = 100; public static boolean PREF_MOUSE_GRAB_FORCE = false; + public static boolean PREF_KEYBOARD_PANNING = true; public static void loadPreferences(Context ctx) { @@ -121,6 +122,7 @@ public static void loadPreferences(Context ctx) { PREF_FORCE_ENABLE_TOUCHCONTROLLER = DEFAULT_PREF.getBoolean("forceEnableTouchController", false); PREF_TOUCHCONTROLLER_VIBRATE_LENGTH = DEFAULT_PREF.getInt("touchControllerVibrateLength", 100); PREF_MOUSE_GRAB_FORCE = DEFAULT_PREF.getBoolean("always_grab_mouse", false); + PREF_KEYBOARD_PANNING = DEFAULT_PREF.getBoolean("keyboardPanning", true); String argLwjglLibname = "-Dorg.lwjgl.opengl.libname="; for (String arg : JREUtils.parseJavaArguments(PREF_CUSTOM_JAVA_ARGS)) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java index 0689a7ce1d..e80011bf20 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java @@ -5,6 +5,7 @@ import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_GYRO_INVERT_X; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_GYRO_INVERT_Y; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_GYRO_SENSITIVITY; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_KEYBOARD_PANNING; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_LONGPRESS_TRIGGER; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_MOUSESPEED; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_MOUSE_GRAB_FORCE; @@ -32,11 +33,11 @@ public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView { private SharedPreferences.Editor mEditor; @SuppressLint("UseSwitchCompatOrMaterialCode") - private Switch mGyroSwitch, mGyroXSwitch, mGyroYSwitch, mGestureSwitch, mMouseGrabSwitch; + private Switch mGyroSwitch, mGyroXSwitch, mGyroYSwitch, mGestureSwitch, mMouseGrabSwitch, mKeyboardPanningSwitch; private CustomSeekbar mGyroSensitivityBar, mMouseSpeedBar, mGestureDelayBar, mResolutionBar; private TextView mGyroSensitivityText, mGyroSensitivityDisplayText, mMouseSpeedText, mGestureDelayText, mGestureDelayDisplayText, mResolutionText; - private boolean mOriginalGyroEnabled, mOriginalGyroXEnabled, mOriginalGyroYEnabled, mOriginalGestureDisabled, mOriginalMouseGrab; + private boolean mOriginalGyroEnabled, mOriginalGyroXEnabled, mOriginalGyroYEnabled, mOriginalGestureDisabled, mOriginalMouseGrab, mOriginalKeyboardPanning; private float mOriginalGyroSensitivity, mOriginalMouseSpeed, mOriginalResolution; private int mOriginalGestureDelay; @@ -67,6 +68,7 @@ private void bindLayout() { mGyroYSwitch = mDialogContent.findViewById(R.id.checkboxGyroY); mGestureSwitch = mDialogContent.findViewById(R.id.checkboxGesture); mMouseGrabSwitch = mDialogContent.findViewById(R.id.always_grab_mouse_side_dialog); + mKeyboardPanningSwitch = mDialogContent.findViewById(R.id.checkboxKeyboardPanning); mGyroSensitivityBar = mDialogContent.findViewById(R.id.editGyro_seekbar); mMouseSpeedBar = mDialogContent.findViewById(R.id.editMouseSpeed_seekbar); @@ -89,6 +91,7 @@ private void setupListeners() { mOriginalGyroYEnabled = PREF_GYRO_INVERT_Y; mOriginalGestureDisabled = PREF_DISABLE_GESTURES; mOriginalMouseGrab = PREF_MOUSE_GRAB_FORCE; + mOriginalKeyboardPanning = PREF_KEYBOARD_PANNING; mOriginalGyroSensitivity = PREF_GYRO_SENSITIVITY; mOriginalMouseSpeed = PREF_MOUSESPEED; @@ -100,6 +103,7 @@ private void setupListeners() { mGyroYSwitch.setChecked(mOriginalGyroYEnabled); mGestureSwitch.setChecked(mOriginalGestureDisabled); mMouseGrabSwitch.setChecked(mOriginalMouseGrab); + mKeyboardPanningSwitch.setChecked(mOriginalKeyboardPanning); mGyroSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { PREF_ENABLE_GYRO = isChecked; @@ -131,6 +135,12 @@ private void setupListeners() { mEditor.putBoolean("always_grab_mouse", isChecked); }); + mKeyboardPanningSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + PREF_KEYBOARD_PANNING = isChecked; + onKeyboardPanningChanged(); + mEditor.putBoolean("keyboardPanning", isChecked); + }); + mGyroSensitivityBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> { PREF_GYRO_SENSITIVITY = progress / 100f; mEditor.putInt("gyroSensitivity", progress); @@ -217,6 +227,7 @@ private void removeListeners() { mGyroYSwitch.setOnCheckedChangeListener(null); mGestureSwitch.setOnCheckedChangeListener(null); mMouseGrabSwitch.setOnCheckedChangeListener(null); + mKeyboardPanningSwitch.setOnCheckedChangeListener(null); mGyroSensitivityBar.setOnSeekBarChangeListener(null); mMouseSpeedBar.setOnSeekBarChangeListener(null); @@ -241,6 +252,7 @@ public void cancel() { PREF_GYRO_INVERT_Y = mOriginalGyroYEnabled; PREF_DISABLE_GESTURES = mOriginalGestureDisabled; PREF_MOUSE_GRAB_FORCE = mOriginalMouseGrab; + PREF_KEYBOARD_PANNING = mOriginalKeyboardPanning; PREF_GYRO_SENSITIVITY = mOriginalGyroSensitivity; PREF_MOUSESPEED = mOriginalMouseSpeed; @@ -249,6 +261,7 @@ public void cancel() { onGyroStateChanged(); onResolutionChanged(); + onKeyboardPanningChanged(); } disappear(true); @@ -257,6 +270,9 @@ public void cancel() { /** Called when the resolution is changed. Use {@link LauncherPreferences#PREF_SCALE_FACTOR} */ public abstract void onResolutionChanged(); + /** Called when the keyboard panning state is changed. Use {@link LauncherPreferences#PREF_KEYBOARD_PANNING} */ + public abstract void onKeyboardPanningChanged(); + /** Called when the gyro state is changed. * Use {@link LauncherPreferences#PREF_ENABLE_GYRO} * Use {@link LauncherPreferences#PREF_GYRO_INVERT_X} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java index ab47473089..41148ed8d6 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerInputView.java @@ -6,6 +6,7 @@ import android.graphics.RectF; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; @@ -39,8 +40,8 @@ import net.kdt.pojavlaunch.EfficientAndroidLWJGLKeycode; import net.kdt.pojavlaunch.customcontrols.keyboard.CharacterSenderStrategy; -public class TouchControllerInputView extends View { - public boolean disableFullScreenInput = false; +public class TouchControllerInputView extends View implements LauncherProxyClient.InputHandler, LauncherProxyClient.KeyboardShowHandler { + public boolean disableFullScreenInput = true; private LauncherProxyClient client; private int width = -1; private int height = -1; @@ -52,7 +53,11 @@ public class TouchControllerInputView extends View { private FloatRect cursorRect; private FloatRect inputAreaRect; private InputConnectionImpl inputConnection; - private final LauncherProxyClient.InputHandler inputHandler; + + public interface InputAreaRectListener { + void updateInputAreaRect(@Nullable RectF rect); + } + private InputAreaRectListener inputAreaRectListener; public TouchControllerInputView(Context context) { this(context, null); @@ -68,57 +73,72 @@ public TouchControllerInputView(Context context, @Nullable AttributeSet attrs, i if (inputMethodManager == null) { throw new IllegalStateException("No InputMethodManager service"); } + } - inputHandler = new LauncherProxyClient.InputHandler() { - @Override - public void updateState(TextInputState textInputState) { - post(() -> { - TextInputState prevState = inputState; - inputState = textInputState; - if (textInputState != null) { - setVisibility(VISIBLE); - setFocusable(true); - } - if (prevState == null && textInputState != null) { - setFocusableInTouchMode(true); - clearFocus(); - requestFocus(); - inputMethodManager.showSoftInput( - TouchControllerInputView.this, - InputMethodManager.SHOW_IMPLICIT - ); - } else if (prevState != null && textInputState == null) { - clearFocus(); - inputMethodManager.hideSoftInputFromWindow( - getWindowToken(), - InputMethodManager.HIDE_IMPLICIT_ONLY - ); - } - if (textInputState != null) { - if (inputConnection != null) { - inputConnection.updateState(textInputState); - } - } else { - setVisibility(GONE); - setFocusable(false); - } - }); - } - @Override - public void updateCursor(FloatRect cursorRect) { - TouchControllerInputView.this.cursorRect = cursorRect; - updateCursorAnchorInfo(); + @Override + public void updateState(TextInputState textInputState) { + post(() -> { + TextInputState prevState = inputState; + inputState = textInputState; + if (textInputState != null) { + setVisibility(VISIBLE); + setFocusable(true); + } + if (prevState == null && textInputState != null) { + setFocusableInTouchMode(true); + clearFocus(); + requestFocus(); + inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); + } else if (prevState != null && textInputState == null) { + clearFocus(); + inputMethodManager.hideSoftInputFromWindow( + getWindowToken(), + InputMethodManager.HIDE_IMPLICIT_ONLY + ); } + if (textInputState != null) { + if (inputConnection != null) { + inputConnection.updateState(textInputState); + } + } else { + setVisibility(GONE); + setFocusable(false); + if (inputAreaRectListener != null) { + inputAreaRectListener.updateInputAreaRect(null); + } + } + }); + } - @Override - public void updateArea(FloatRect inputAreaRect) { - TouchControllerInputView.this.inputAreaRect = inputAreaRect; - updateCursorAnchorInfo(); + @Override + public void updateCursor(FloatRect cursorRect) { + post(() -> { + TouchControllerInputView.this.cursorRect = cursorRect; + updateCursorAnchorInfo(); + }); + } + + @Override + public void updateArea(FloatRect inputAreaRect) { + post(() -> { + TouchControllerInputView.this.inputAreaRect = inputAreaRect; + updateCursorAnchorInfo(); + }); + } + + @Override + public void showKeyboard() { + post(() -> { + if (inputState != null) { + inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); } - }; + }); } + @Override + public void hideKeyboard() {} + private static boolean isEmpty(TextRange range) { return range.getLength() == 0; } @@ -143,10 +163,12 @@ public void setClient(LauncherProxyClient value) { LauncherProxyClient prev = this.client; if (prev != null) { prev.setInputHandler(null); + prev.setKeyboardShowHandler(null); } this.client = value; if (value != null) { - value.setInputHandler(inputHandler); + value.setInputHandler(this); + value.setKeyboardShowHandler(this); } } @@ -155,6 +177,10 @@ public void setSize(int width, int height) { this.height = height; } + public void setInputAreaRectListener(InputAreaRectListener listener) { + inputAreaRectListener = listener; + } + @Override public boolean onCheckIsTextEditor() { return true; @@ -198,16 +224,18 @@ private void updateCursorAnchorInfo() { ); } if (inputAreaRect != null) { + RectF rect = new RectF( + inputAreaRect.getLeft() * width, + inputAreaRect.getTop() * height, + (inputAreaRect.getLeft() + inputAreaRect.getWidth()) * width, + (inputAreaRect.getTop() + inputAreaRect.getHeight()) * height + ); + if (inputAreaRectListener != null) { + inputAreaRectListener.updateInputAreaRect(rect); + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { EditorBoundsInfo editorBoundsInfo = new EditorBoundsInfo.Builder() - .setEditorBounds( - new RectF( - inputAreaRect.getLeft() * width, - inputAreaRect.getTop() * height, - (inputAreaRect.getLeft() + inputAreaRect.getWidth()) * width, - (inputAreaRect.getTop() + inputAreaRect.getHeight()) * height - ) - ) + .setEditorBounds(rect) .build(); builder.setEditorBoundsInfo(editorBoundsInfo); } @@ -307,6 +335,9 @@ public boolean clearMetaKeyStates(int states) { @Override public void closeConnection() { + if (inputAreaRectListener != null) { + inputAreaRectListener.updateInputAreaRect(null); + } } @Override @@ -489,7 +520,7 @@ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { @Nullable @Override - public android.os.Handler getHandler() { + public Handler getHandler() { return null; } @@ -562,12 +593,12 @@ public boolean performContextMenuAction(int id) { @Override public boolean performEditorAction(int editorAction) { - return false; + return true; } @Override public boolean performPrivateCommand(String action, Bundle data) { - return false; + return true; } @Override diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerUtils.java index fa3a3b60a4..9335f5624f 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/TouchControllerUtils.java @@ -106,7 +106,7 @@ public static void initialize(Context context, TouchControllerInputView touchCon Log.w("TouchController", "Failed to set TouchController environment variable", e); } MessageTransport transport = UnixSocketTransportKt.UnixSocketTransport(socketName); - proxyClient = new LauncherProxyClient(transport, Set.of(PlatformCapability.TEXT_STATUS)); + proxyClient = new LauncherProxyClient(transport, Set.of(PlatformCapability.TEXT_STATUS, PlatformCapability.KEYBOARD_SHOW)); proxyClient.run(); touchControllerInputView.setClient(proxyClient); Vibrator vibrator = ContextCompat.getSystemService(context, Vibrator.class); diff --git a/app_pojavlauncher/src/main/res/layout/activity_basemain.xml b/app_pojavlauncher/src/main/res/layout/activity_basemain.xml index ac34158ccc..b258b96200 100644 --- a/app_pojavlauncher/src/main/res/layout/activity_basemain.xml +++ b/app_pojavlauncher/src/main/res/layout/activity_basemain.xml @@ -38,11 +38,24 @@ android:translationZ="1dp" android:visibility="gone"/> + + + android:layout_height="1dp" + android:layout_gravity="bottom" /> + + \ No newline at end of file diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml index 6f695e916d..ce70ec7288 100644 --- a/app_pojavlauncher/src/main/res/values/strings.xml +++ b/app_pojavlauncher/src/main/res/values/strings.xml @@ -176,6 +176,8 @@ Changes the speed of the virtual mouse. Use virtual cursor Ensures the cursor stays inside the game. Prevents mouse from clicking touch control layout. + Keyboard panning + Shift the screen upwards when the keyboard is open. Mouse pass-thru Swipeable Forward lock diff --git a/app_pojavlauncher/src/main/res/xml/pref_control.xml b/app_pojavlauncher/src/main/res/xml/pref_control.xml index 7747d0c7a4..5dc60d68a8 100644 --- a/app_pojavlauncher/src/main/res/xml/pref_control.xml +++ b/app_pojavlauncher/src/main/res/xml/pref_control.xml @@ -62,6 +62,11 @@ android:title="@string/mcl_setting_title_buttonallcaps" android:summary="@string/mcl_setting_subtitle_buttonallcaps" /> +