Repository: PojavLauncherTeam/PojavLauncher (language: java)

Issue:
Mouse scaling set to 25%
### Describe the bug

The "Mouse scaling" setting is broken, it set to 25%

### The log file and images/videos

[Video](https://github.com/user-attachments/assets/72ac7067-9c8b-4465-af93-59151f2a75bb)
**No log file**


### Steps To Reproduce

```markdown
1. Open PojavLauncher
2. Click the Settings
3. Go to "Control customization"
4. Change the "Mouse scaling"
5. Back
6. Check the Mouse scaling again
```


### Expected Behavior

I expected it to not set to 25% and I can set it to 75%

### Platform

```markdown
- Device model: Realme 5 Pro(RMX1971)
- CPU architecture: aarch64
- Android version: 11
- PojavLauncher version: foxglove-20241122-19dc51f-feat/quick_settings
```


### Anything else?

_No response_


Discussion / hints:

Fix patch:
diff --git a/app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar b/app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar
index d7a05d5e41..4141a08bb8 100644
Binary files a/app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar and b/app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar differ
diff --git a/app_pojavlauncher/src/main/assets/components/lwjgl3/version b/app_pojavlauncher/src/main/assets/components/lwjgl3/version
index c3b857f256..a4b0232d5d 100644
--- a/app_pojavlauncher/src/main/assets/components/lwjgl3/version
+++ b/app_pojavlauncher/src/main/assets/components/lwjgl3/version
@@ -1,1 +1,1 @@
-1720909595576
\ No newline at end of file
+1732218529630
\ No newline at end of file
diff --git a/app_pojavlauncher/src/main/java/com/kdt/CustomSeekbar.java b/app_pojavlauncher/src/main/java/com/kdt/CustomSeekbar.java
new file mode 100644
index 0000000000..050276e3e2
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/com/kdt/CustomSeekbar.java
@@ -0,0 +1,138 @@
+package com.kdt;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+/**
+ * Seekbar with ability to handle ranges and increments
+ */
+@SuppressLint("AppCompatCustomView")
+public class CustomSeekbar extends SeekBar {
+    private int mMin = 0;
+    private int mIncrement = 1;
+    private SeekBar.OnSeekBarChangeListener mListener;
+
+    /** When using increments, this flag is used to prevent double calls to the listener */
+    private boolean mInternalChanges = false;
+
+    public CustomSeekbar(Context context) {
+        super(context);
+        setup();
+    }
+
+    public CustomSeekbar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setup();
+    }
+
+    public CustomSeekbar(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setup();
+    }
+
+    public CustomSeekbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        setup();
+    }
+
+    public void setIncrement(int increment) {
+        mIncrement = increment;
+    }
+
+    public void setRange(int min, int max) {
+        mMin = min;
+        setMax(max - min);
+    }
+
+    @Override
+    public synchronized void setProgress(int progress) {
+        super.setProgress(applyIncrement(progress - mMin));
+    }
+
+    @Override
+    public void setProgress(int progress, boolean animate) {
+        super.setProgress(applyIncrement(progress - mMin), animate);
+    }
+
+    @Override
+    public synchronized int getProgress() {
+        return applyIncrement(super.getProgress() + mMin);
+    }
+
+    @Override
+    public synchronized void setMin(int min) {
+        super.setMin(min);
+        mMin = min;
+    }
+
+    /**
+     * Wrapper to allow for a listener to be set around the internal listener
+     */
+    @Override
+    public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) {
+        mListener = l;
+    }
+
+    public void setup() {
+        super.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+            /** Store the previous progress to prevent double calls with increments */
+            private int previousProgress = 0;
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (mInternalChanges) return;
+                mInternalChanges = true;
+
+                progress += mMin;
+                progress = applyIncrement(progress);
+
+                if (progress != previousProgress) {
+                    if (mListener != null) {
+                        previousProgress = progress;
+                        mListener.onProgressChanged(seekBar, progress, fromUser);
+                    }
+                }
+
+                // Forces the thumb to snap to the increment
+                setProgress(progress);
+                mInternalChanges = false;
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+                if (mInternalChanges) return;
+
+                if (mListener != null) {
+                    mListener.onStartTrackingTouch(seekBar);
+                }
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                if (mInternalChanges) return;
+                mInternalChanges = true;
+
+                setProgress(seekBar.getProgress());
+
+                if (mListener != null) {
+                    mListener.onStopTrackingTouch(seekBar);
+                }
+                mInternalChanges = false;
+            }
+        });
+    }
+
+    /**
+     * Apply increment to the progress
+     * @param progress Progress to apply increment to
+     * @return Progress with increment applied
+     */
+    private int applyIncrement(int progress) {
+        if (mIncrement < 1) return progress;
+
+        progress = progress / mIncrement;
+        progress = progress * mIncrement;
+        return progress;
+    }
+}
diff --git a/app_pojavlauncher/src/main/java/com/kdt/SideDialogView.java b/app_pojavlauncher/src/main/java/com/kdt/SideDialogView.java
new file mode 100644
index 0000000000..5570295105
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/com/kdt/SideDialogView.java
@@ -0,0 +1,246 @@
+package com.kdt;
+
+import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.core.content.res.ResourcesCompat;
+
+import net.kdt.pojavlaunch.R;
+import net.kdt.pojavlaunch.Tools;
+
+/**
+ * The base class for side dialog views
+ * A side dialog is a dialog appearing from one side of the screen
+ */
+public abstract class SideDialogView {
+
+    private final ViewGroup mParent;
+    private final @LayoutRes int mLayoutId;
+    private ViewGroup mDialogLayout;
+    private DefocusableScrollView mScrollView;
+    protected View mDialogContent;
+
+    protected final int mMargin;
+    private ObjectAnimator mSideDialogAnimator;
+    protected boolean mDisplaying = false;
+    /* Whether the layout is built */
+    private boolean mIsInstantiated = false;
+
+    /* UI elements */
+    private Button mStartButton, mEndButton;
+    private TextView mTitleTextview;
+    private View mTitleDivider;
+
+    /* Data to store when the UI element has yet to be inflated */
+    private @StringRes int mStartButtonStringId, mEndButtonStringId, mTitleStringId;
+    private View.OnClickListener mStartButtonListener, mEndButtonListener;
+
+
+    public SideDialogView(Context context, ViewGroup parent, @LayoutRes int layoutId) {
+        mMargin = context.getResources().getDimensionPixelOffset(R.dimen._20sdp);
+        mParent = parent;
+        mLayoutId = layoutId;
+    }
+
+    public void setTitle(@StringRes int textId) {
+        mTitleStringId = textId;
+        if (mIsInstantiated) {
+            mTitleTextview.setText(textId);
+            mTitleTextview.setVisibility(View.VISIBLE);
+            mTitleDivider.setVisibility(View.VISIBLE);
+        }
+    }
+
+    public final void setStartButtonListener(@StringRes int textId, @Nullable View.OnClickListener listener) {
+        mStartButtonStringId = textId;
+        mStartButtonListener = listener;
+        if (mIsInstantiated) setButton(mStartButton, textId, listener);
+    }
+
+    public final void setEndButtonListener(@StringRes int textId, @Nullable View.OnClickListener listener) {
+        mEndButtonStringId = textId;
+        mEndButtonListener = listener;
+        if (mIsInstantiated) setButton(mEndButton, textId, listener);
+    }
+
+    private void setButton(@NonNull Button button, @StringRes int textId, @Nullable View.OnClickListener listener) {
+        button.setText(textId);
+        button.setOnClickListener(listener);
+        button.setVisibility(View.VISIBLE);
+    }
+
+
+    private void inflateLayout() {
+        if(mIsInstantiated) {
+            Log.w("SideDialogView", "Layout already inflated");
+            return;
+        }
+
+        // Inflate layouts
+        mDialogLayout = (ViewGroup) LayoutInflater.from(mParent.getContext()).inflate(R.layout.dialog_side_dialog, mParent, false);
+        mScrollView = mDialogLayout.findViewById(R.id.side_dialog_scrollview);
+        mStartButton = mDialogLayout.findViewById(R.id.side_dialog_start_button);
+        mEndButton = mDialogLayout.findViewById(R.id.side_dialog_end_button);
+        mTitleTextview = mDialogLayout.findViewById(R.id.side_dialog_title_textview);
+        mTitleDivider = mDialogLayout.findViewById(R.id.side_dialog_title_divider);
+
+        LayoutInflater.from(mParent.getContext()).inflate(mLayoutId, mScrollView, true);
+        mDialogContent = mScrollView.getChildAt(0);
+
+        // Attach layouts
+        mParent.addView(mDialogLayout);
+
+        mSideDialogAnimator = ObjectAnimator.ofFloat(mDialogLayout, "x", 0).setDuration(600);
+        mSideDialogAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+
+        mDialogLayout.setElevation(10);
+        mDialogLayout.setTranslationZ(10);
+
+        mDialogLayout.setVisibility(View.VISIBLE);
+        mDialogLayout.setBackground(ResourcesCompat.getDrawable(mDialogLayout.getResources(), R.drawable.background_control_editor, null));
+
+        //TODO offset better according to view width
+        mDialogLayout.setX(-mDialogLayout.getResources().getDimensionPixelOffset(R.dimen._280sdp));
+        mIsInstantiated = true;
+
+        // Set up UI elements
+        if (mTitleStringId != 0) setTitle(mTitleStringId);
+        if (mStartButtonStringId != 0) setStartButtonListener(mStartButtonStringId, mStartButtonListener);
+        if (mEndButtonStringId != 0) setEndButtonListener(mEndButtonStringId, mEndButtonListener);
+    }
+
+    /** Destroy the layout, cleanup variables */
+    private void deflateLayout() {
+        if(!mIsInstantiated) {
+            Log.w("SideDialogView", "Layout not inflated");
+            return;
+        }
+
+        mParent.removeView(mDialogLayout);
+        mIsInstantiated = false;
+
+        mScrollView = null;
+        mSideDialogAnimator = null;
+        mDialogLayout = null;
+        mDialogContent = null;
+        mTitleTextview = null;
+        mTitleDivider = null;
+        mStartButton = null;
+        mEndButton = null;
+    }
+
+
+    /**
+     * Slide the layout into the visible screen area
+     */
+    @CallSuper
+    public final void appear(boolean fromRight) {
+        if (!mIsInstantiated) {
+            inflateLayout();
+            onInflate();
+        }
+
+        // To avoid UI sizing issue when the dialog is not fully inflated
+        onAppear();
+        Tools.runOnUiThread(() -> {
+            if (fromRight) {
+                if (!mDisplaying || !isAtRight()) {
+                    mSideDialogAnimator.setFloatValues(currentDisplayMetrics.widthPixels, currentDisplayMetrics.widthPixels - mScrollView.getWidth() - mMargin);
+                    mSideDialogAnimator.start();
+                    mDisplaying = true;
+                }
+            } else {
+                if (!mDisplaying || isAtRight()) {
+                    mSideDialogAnimator.setFloatValues(-mDialogLayout.getWidth(), mMargin);
+                    mSideDialogAnimator.start();
+                    mDisplaying = true;
+                }
+            }
+        });
+    }
+
+    protected final boolean isAtRight() {
+        return mDialogLayout.getX() > currentDisplayMetrics.widthPixels / 2f;
+    }
+
+    /**
+     * Slide out the layout
+     * @param destroy Whether the layout should be destroyed after disappearing.
+     *                Recommended to be true if the layout is not going to be used anymore
+     */
+    @CallSuper
+    public final void disappear(boolean destroy) {
+        if (!mDisplaying) {
+            if(destroy) {
+                onDisappear();
+                onDestroy();
+                deflateLayout();
+            }
+            return;
+        }
+
+        mDisplaying = false;
+        if (isAtRight())
+            mSideDialogAnimator.setFloatValues(currentDisplayMetrics.widthPixels - mDialogLayout.getWidth() - mMargin, currentDisplayMetrics.widthPixels);
+        else
+            mSideDialogAnimator.setFloatValues(mMargin, -mDialogLayout.getWidth());
+
+        if(destroy) {
+            onDisappear();
+            onDestroy();
+            mSideDialogAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mSideDialogAnimator.removeListener(this);
+                    deflateLayout();
+                }
+            });
+        }
+
+        mSideDialogAnimator.start();
+    }
+
+    /** @return Whether the dialog is currently displaying */
+    public final boolean isDisplaying(){
+        return mDisplaying;
+    }
+
+    /**
+     * Called when the dialog is inflated, ideal for setting up UI elements bindings
+     */
+    protected void onInflate() {}
+
+    /**
+     * Called after the dialog has appeared
+     */
+    protected void onAppear() {}
+
+    /**
+     * Called after the dialog has disappeared
+     */
+    protected void onDisappear() {}
+
+    /**
+     * Called before the dialog gets destroyed (removing views from parent)
+     * Ideal for cleaning up resources
+     */
+    protected void onDestroy() {}
+
+
+}
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java
index 9e7d99c97d..6db1ccbf2f 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java
@@ -89,8 +89,8 @@ protected void onCreate(Bundle savedInstanceState) {
 
         mMousePointerImageView.post(() -> {
             ViewGroup.LayoutParams params = mMousePointerImageView.getLayoutParams();
-            params.width = (int) (36 / 100f * LauncherPreferences.PREF_MOUSESCALE);
-            params.height = (int) (54 / 100f * LauncherPreferences.PREF_MOUSESCALE);
+            params.width = (int) (36 * LauncherPreferences.PREF_MOUSESCALE);
+            params.height = (int) (54 * LauncherPreferences.PREF_MOUSESCALE);
         });
 
         mTouchPad.setOnTouchListener(new View.OnTouchListener() {
@@ -177,7 +177,7 @@ public boolean onTouch(View v, MotionEvent event) {
         getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
             @Override
             public void handleOnBackPressed() {
-                MainActivity.dialogForceClose(JavaGUILauncherActivity.this);
+                Tools.dialogForceClose(JavaGUILauncherActivity.this);
             }
         });
     }
@@ -336,7 +336,7 @@ void sendScaledMousePosition(float x, float y){
     }
 
     public void forceClose(View v) {
-        MainActivity.dialogForceClose(this);
+        Tools.dialogForceClose(this);
     }
 
     public void openLogOutput(View v) {
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java
index 766c835dc3..50eb96e747 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java
@@ -1,6 +1,7 @@
 package net.kdt.pojavlaunch;
 
 import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
+import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_ENABLE_GYRO;
 import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_SUSTAINED_PERFORMANCE;
 import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_USE_ALTERNATE_SURFACE;
 import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_VIRTUAL_MOUSE_START;
@@ -27,15 +28,12 @@
 import android.util.Log;
 import android.view.InputDevice;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.webkit.MimeTypeMap;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.ListView;
-import android.widget.SeekBar;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
@@ -55,9 +53,11 @@
 import net.kdt.pojavlaunch.customcontrols.keyboard.LwjglCharSender;
 import net.kdt.pojavlaunch.customcontrols.keyboard.TouchCharInput;
 import net.kdt.pojavlaunch.customcontrols.mouse.GyroControl;
+import net.kdt.pojavlaunch.customcontrols.mouse.HotbarView;
 import net.kdt.pojavlaunch.customcontrols.mouse.Touchpad;
 import net.kdt.pojavlaunch.lifecycle.ContextExecutor;
 import net.kdt.pojavlaunch.prefs.LauncherPreferences;
+import net.kdt.pojavlaunch.prefs.QuickSettingSideDialog;
 import net.kdt.pojavlaunch.services.GameService;
 import net.kdt.pojavlaunch.utils.JREUtils;
 import net.kdt.pojavlaunch.utils.MCOptionUtils;
@@ -84,7 +84,8 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
     private ListView navDrawer;
     private View mDrawerPullButton;
     private GyroControl mGyroControl = null;
-    public static ControlLayout mControlLayout;
+    private ControlLayout mControlLayout;
+    private HotbarView mHotbarView;
 
     MinecraftProfile minecraftProfile;
 
@@ -94,6 +95,8 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
     public AdapterView.OnItemClickListener ingameControlsEditorListener;
     private GameService.LocalBinder mServiceBinder;
 
+    private QuickSettingSideDialog mQuickSettingSideDialog;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -106,7 +109,8 @@ public void onCreate(Bundle savedInstanceState) {
         initLayout(R.layout.activity_basemain);
         CallbackBridge.addGrabListener(touchpad);
         CallbackBridge.addGrabListener(minecraftGLView);
-        if(LauncherPreferences.PREF_ENABLE_GYRO) mGyroControl = new GyroControl(this);
+
+        mGyroControl = new GyroControl(this);
 
         // Enabling this on TextureView results in a broken white result
         if(PREF_USE_ALTERNATE_SURFACE) getWindow().setBackgroundDrawable(null);
@@ -185,12 +189,10 @@ protected void initLayout(int resId) {
                     android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.menu_ingame));
             gameActionClickListener = (parent, view, position, id) -> {
                 switch(position) {
-                    case 0: dialogForceClose(MainActivity.this); break;
-                    case 1: openLogOutput(); break;
-                    case 2: dialogSendCustomKey(); break;
-                    case 3: adjustMouseSpeedLive(); break;
-                    case 4: adjustGyroSensitivityLive(); break;
-                    case 5: openCustomControls(); break;
+                    case 0: openLogOutput(); break;
+                    case 1: dialogSendCustomKey(); break;
+                    case 2: openQuickSettings(); break;
+                    case 3: openCustomControls(); break;
                 }
                 drawerLayout.closeDrawers();
             };
@@ -198,7 +200,6 @@ protected void initLayout(int resId) {
             navDrawer.setOnItemClickListener(gameActionClickListener);
             drawerLayout.closeDrawers();
 
-
             final String finalVersion = version;
             minecraftGLView.setSurfaceReadyListener(() -> {
                 try {
@@ -240,8 +241,12 @@ private void loadControls() {
 
     @Override
     public void onAttachedToWindow() {
-        LauncherPreferences.computeNotchSize(this);
-        loadControls();
+        // Post to get the correct display dimensions after layout.
+        mControlLayout.post(()->{
+            LauncherPreferences.computeNotchSize(this);
+            Tools.getDisplayMetrics(this);
+            loadControls();
+        });
     }
 
     /** Boilerplate binding */
@@ -255,21 +260,25 @@ private void bindValues(){
         mControlLayout = findViewById(R.id.main_control_layout);
         touchCharInput = findViewById(R.id.mainTouchCharInput);
         mDrawerPullButton = findViewById(R.id.drawer_button);
+        mHotbarView = findViewById(R.id.hotbar_view);
     }
 
     @Override
     public void onResume() {
         super.onResume();
-        if(mGyroControl != null) mGyroControl.enable();
+        if(PREF_ENABLE_GYRO) mGyroControl.enable();
         CallbackBridge.nativeSetWindowAttrib(LwjglGlfwKeycode.GLFW_HOVERED, 1);
     }
 
     @Override
     protected void onPause() {
-        if(mGyroControl != null) mGyroControl.disable();
+        mGyroControl.disable();
         if (CallbackBridge.isGrabbing()){
             sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_ESCAPE);
         }
+        if(mQuickSettingSideDialog != null) {
+            mQuickSettingSideDialog.cancel();
+        }
         CallbackBridge.nativeSetWindowAttrib(LwjglGlfwKeycode.GLFW_HOVERED, 0);
         super.onPause();
     }
@@ -299,9 +308,16 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
 
         if(mGyroControl != null) mGyroControl.updateOrientation();
-        Tools.updateWindowSize(this);
-        minecraftGLView.refreshSize();
-        runOnUiThread(() -> mControlLayout.refreshControlButtonPositions());
+        // Layout resize is practically guaranteed on a configuration change, and `onConfigurationChanged`
+        // does not implicitly start a layout. So, request a layout and expect the screen dimensions to be valid after the]
+        // post.
+        mControlLayout.requestLayout();
+        mControlLayout.post(()->{
+            // Child of mControlLayout, so refreshing size here is correct
+            minecraftGLView.refreshSize();
+            Tools.updateWindowSize(this);
+            mControlLayout.refreshControlButtonPositions();
+        });
     }
 
     @Override
@@ -326,14 +342,6 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         }
     }
 
-    public static void fullyExit() {
-        android.os.Process.killProcess(android.os.Process.myPid());
-    }
-
-    public static boolean isAndroid8OrHigher() {
-        return Build.VERSION.SDK_INT >= 26;
-    }
-
     private void runCraft(String versionId, JMinecraftVersionList.Version version) throws Throwable {
         if(Tools.LOCAL_RENDERER == null) {
             Tools.LOCAL_RENDERER = LauncherPreferences.PREF_RENDERER;
@@ -347,7 +355,7 @@ private void runCraft(String versionId, JMinecraftVersionList.Version version) t
         }
         MinecraftAccount minecraftAccount = PojavProfile.getCurrentProfileContent(this, null);
         Logger.appendToLog("--------- Starting game with Launcher Debug!");
-        printLauncherInfo(versionId, Tools.isValidString(minecraftProfile.javaArgs) ? minecraftProfile.javaArgs : LauncherPreferences.PREF_CUSTOM_JAVA_ARGS);
+        Tools.printLauncherInfo(versionId, Tools.isValidString(minecraftProfile.javaArgs) ? minecraftProfile.javaArgs : LauncherPreferences.PREF_CUSTOM_JAVA_ARGS);
         JREUtils.redirectAndPrintJRELog();
         LauncherProfiles.load();
         int requiredJavaVersion = 8;
@@ -357,15 +365,6 @@ private void runCraft(String versionId, JMinecraftVersionList.Version version) t
         Tools.runOnUiThread(()-> mServiceBinder.isActive = false);
     }
 
-    private void printLauncherInfo(String gameVersion, String javaArguments) {
-        Logger.appendToLog("Info: Launcher version: " + BuildConfig.VERSION_NAME);
-        Logger.appendToLog("Info: Architecture: " + Architecture.archAsString(Tools.DEVICE_ARCHITECTURE));
-        Logger.appendToLog("Info: Device model: " + Build.MANUFACTURER + " " +Build.MODEL);
-        Logger.appendToLog("Info: API version: " + Build.VERSION.SDK_INT);
-        Logger.appendToLog("Info: Selected Minecraft version: " + gameVersion);
-        Logger.appendToLog("Info: Custom Java arguments: \"" + javaArguments + "\"");
-    }
-
     private void dialogSendCustomKey() {
         AlertDialog.Builder dialog = new AlertDialog.Builder(this);
         dialog.setTitle(R.string.control_customkey);
@@ -388,6 +387,29 @@ private void openLogOutput() {
         loggerView.setVisibility(View.VISIBLE);
     }
 
+    private void openQuickSettings() {
+        if(mQuickSettingSideDialog == null) {
+            mQuickSettingSideDialog = new QuickSettingSideDialog(this, mControlLayout) {
+                @Override
+                public void onResolutionChanged() {
+                    minecraftGLView.refreshSize();
+                    mHotbarView.onResolutionChanged();
+                }
+
+                @Override
+                public void onGyroStateChanged() {
+                    mGyroControl.updateOrientation();
+                    if (PREF_ENABLE_GYRO) {
+                        mGyroControl.enable();
+                    } else {
+                        mGyroControl.disable();
+                    }
+                }
+            };
+        }
+        mQuickSettingSideDialog.appear(true);
+    }
+
     public static void toggleMouse(Context ctx) {
         if (CallbackBridge.isGrabbing()) return;
 
@@ -396,19 +418,6 @@ public static void toggleMouse(Context ctx) {
                 Toast.LENGTH_SHORT).show();
     }
 
-    public static void dialogForceClose(Context ctx) {
-        new AlertDialog.Builder(ctx)
-                .setMessage(R.string.mcn_exit_confirm)
-                .setNegativeButton(android.R.string.cancel, null)
-                .setPositiveButton(android.R.string.ok, (p1, p2) -> {
-                    try {
-                        fullyExit();
-                    } catch (Throwable th) {
-                        Log.w(Tools.APP_NAME, "Could not enable System.exit() method!", th);
-                    }
-                }).show();
-    }
-
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         if(isInEditor) {
@@ -433,83 +442,6 @@ public static void switchKeyboardState() {
         if(touchCharInput != null) touchCharInput.switchKeyboardState();
     }
 
-
-    int tmpMouseSpeed;
-    public void adjustMouseSpeedLive() {
-        AlertDialog.Builder b = new AlertDialog.Builder(this);
-        b.setTitle(R.string.mcl_setting_title_mousespeed);
-        View v = LayoutInflater.from(this).inflate(R.layout.dialog_live_mouse_speed_editor,null);
-        final SeekBar sb = v.findViewById(R.id.mouseSpeed);
-        final TextView tv = v.findViewById(R.id.mouseSpeedTV);
-        sb.setMax(275);
-        tmpMouseSpeed = (int) ((LauncherPreferences.PREF_MOUSESPEED*100));
-        sb.setProgress(tmpMouseSpeed-25);
-        tv.setText(getString(R.string.percent_format, tmpMouseSpeed));
-        sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
-                tmpMouseSpeed = i+25;
-                tv.setText(getString(R.string.percent_format, tmpMouseSpeed));
-            }
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {}
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {}
-        });
-        b.setView(v);
-        b.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
-            LauncherPreferences.PREF_MOUSESPEED = ((float)tmpMouseSpeed)/100f;
-            LauncherPreferences.DEFAULT_PREF.edit().putInt("mousespeed",tmpMouseSpeed).apply();
-            dialogInterface.dismiss();
-            System.gc();
-        });
-        b.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> {
-            dialogInterface.dismiss();
-            System.gc();
-        });
-        b.show();
-    }
-
-    int tmpGyroSensitivity;
-    public void adjustGyroSensitivityLive() {
-        if(!LauncherPreferences.PREF_ENABLE_GYRO) {
-            Toast.makeText(this, R.string.toast_turn_on_gyro, Toast.LENGTH_LONG).show();
-            return;
-        }
-        AlertDialog.Builder b = new AlertDialog.Builder(this);
-        b.setTitle(R.string.preference_gyro_sensitivity_title);
-        View v = LayoutInflater.from(this).inflate(R.layout.dialog_live_mouse_speed_editor,null);
-        final SeekBar sb = v.findViewById(R.id.mouseSpeed);
-        final TextView tv = v.findViewById(R.id.mouseSpeedTV);
-        sb.setMax(275);
-        tmpGyroSensitivity = (int) ((LauncherPreferences.PREF_GYRO_SENSITIVITY*100));
-        sb.setProgress(tmpGyroSensitivity -25);
-        tv.setText(getString(R.string.percent_format, tmpGyroSensitivity));
-        sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
-                tmpGyroSensitivity = i+25;
-                tv.setText(getString(R.string.percent_format, tmpGyroSensitivity));
-            }
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {}
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {}
-        });
-        b.setView(v);
-        b.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
-            LauncherPreferences.PREF_GYRO_SENSITIVITY = ((float) tmpGyroSensitivity)/100f;
-            LauncherPreferences.DEFAULT_PREF.edit().putInt("gyroSensitivity", tmpGyroSensitivity).apply();
-            dialogInterface.dismiss();
-            System.gc();
-        });
-        b.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> {
-            dialogInterface.dismiss();
-            System.gc();
-        });
-        b.show();
-    }
-
     public static void openLink(String link) {
         Context ctx = touchpad.getContext(); // no more better way to obtain a context statically
         ((Activity)ctx).runOnUiThread(() -> {
@@ -582,10 +514,10 @@ public void onClickedMenu() {
     @Override
     public void exitEditor() {
         try {
-            MainActivity.mControlLayout.loadLayout((CustomControls)null);
-            MainActivity.mControlLayout.setModifiable(false);
+            mControlLayout.loadLayout((CustomControls)null);
+            mControlLayout.setModifiable(false);
             System.gc();
-            MainActivity.mControlLayout.loadLayout(
+            mControlLayout.loadLayout(
                     minecraftProfile.controlFile == null
                             ? LauncherPreferences.PREF_DEFAULTCTRL_PATH
                             : Tools.CTRLMAP_PATH + "/" + minecraftProfile.controlFile);
@@ -593,7 +525,7 @@ public void exitEditor() {
         } catch (IOException e) {
             Tools.showError(this,e);
         }
-        //((MainActivity) this).mControlLayout.loadLayout((CustomControls)null);
+
         navDrawer.setAdapter(gameActionArrayAdapter);
         navDrawer.setOnItemClickListener(gameActionClickListener);
         isInEditor = false;
@@ -629,7 +561,7 @@ private boolean checkCaptureDispatchConditions(MotionEvent event) {
 
     @Override
     public boolean dispatchTrackballEvent(MotionEvent ev) {
-        if(MainActivity.isAndroid8OrHigher() && checkCaptureDispatchConditions(ev))
+        if(Tools.isAndroid8OrHigher() && checkCaptureDispatchConditions(ev))
             return minecraftGLView.dispatchCapturedPointerEvent(ev);
         else return super.dispatchTrackballEvent(ev);
     }
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java
index 2c460558f9..dd9f5f10f2 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java
@@ -66,8 +66,6 @@ public class MinecraftGLSurface extends View implements GrabListener {
             .remapRightTrigger(true)
             .remapDpad(true));
 
-    /* Resolution scaler option, allow downsizing a window */
-    private final float mScaleFactor = LauncherPreferences.PREF_SCALE_FACTOR/100f;
     /* Sensitivity, adjusted according to screen size */
     private final double mSensitivityFactor = (1.4 * (1080f/ Tools.getDisplayMetrics((Activity) getContext()).heightPixels));
 
@@ -78,7 +76,7 @@ public class MinecraftGLSurface extends View implements GrabListener {
     View mSurface;
 
     private final InGameEventProcessor mIngameProcessor = new InGameEventProcessor(mSensitivityFactor);
-    private final InGUIEventProcessor mInGUIProcessor = new InGUIEventProcessor(mScaleFactor);
+    private final InGUIEventProcessor mInGUIProcessor = new InGUIEventProcessor();
     private TouchEventProcessor mCurrentTouchProcessor = mInGUIProcessor;
     private AndroidPointerCapture mPointerCapture;
     private boolean mLastGrabState = false;
@@ -95,7 +93,7 @@ public MinecraftGLSurface(Context context, AttributeSet attributeSet) {
     @RequiresApi(api = Build.VERSION_CODES.O)
     private void setUpPointerCapture(AbstractTouchpad touchpad) {
         if(mPointerCapture != null) mPointerCapture.detach();
-        mPointerCapture = new AndroidPointerCapture(touchpad, this, mScaleFactor);
+        mPointerCapture = new AndroidPointerCapture(touchpad, this);
     }
 
     /** Initialize the view and all its settings
@@ -105,7 +103,7 @@ private void setUpPointerCapture(AbstractTouchpad touchpad) {
      *                 when the cursor is not grabbed
      */
     public void start(boolean isAlreadyRunning, AbstractTouchpad touchpad){
-        if(MainActivity.isAndroid8OrHigher()) setUpPointerCapture(touchpad);
+        if(Tools.isAndroid8OrHigher()) setUpPointerCapture(touchpad);
         mInGUIProcessor.setAbstractTouchpad(touchpad);
         if(LauncherPreferences.PREF_USE_ALTERNATE_SURFACE){
             SurfaceView surfaceView = new SurfaceView(getContext());
@@ -188,7 +186,7 @@ public boolean onTouchEvent(MotionEvent e) {
         for (int i = 0; i < e.getPointerCount(); i++) {
             int toolType = e.getToolType(i);
             if(toolType == MotionEvent.TOOL_TYPE_MOUSE) {
-                if(MainActivity.isAndroid8OrHigher() &&
+                if(Tools.isAndroid8OrHigher() &&
                         mPointerCapture != null) {
                     mPointerCapture.handleAutomaticCapture();
                     return true;
@@ -197,7 +195,7 @@ public boolean onTouchEvent(MotionEvent e) {
 
             // Mouse found
             if(CallbackBridge.isGrabbing()) return false;
-            CallbackBridge.sendCursorPos(   e.getX(i) * mScaleFactor, e.getY(i) * mScaleFactor);
+            CallbackBridge.sendCursorPos(   e.getX(i) * LauncherPreferences.PREF_SCALE_FACTOR, e.getY(i) * LauncherPreferences.PREF_SCALE_FACTOR);
             return true; //mouse event handled successfully
         }
         if (mIngameProcessor == null || mInGUIProcessor == null) return true;
@@ -236,8 +234,8 @@ public boolean dispatchGenericMotionEvent(MotionEvent event) {
 
         switch(event.getActionMasked()) {
             case MotionEvent.ACTION_HOVER_MOVE:
-                CallbackBridge.mouseX = (event.getX(mouseCursorIndex) * mScaleFactor);
-                CallbackBridge.mouseY = (event.getY(mouseCursorIndex) * mScaleFactor);
+                CallbackBridge.mouseX = (event.getX(mouseCursorIndex) * LauncherPreferences.PREF_SCALE_FACTOR);
+                CallbackBridge.mouseY = (event.getY(mouseCursorIndex) * LauncherPreferences.PREF_SCALE_FACTOR);
                 CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
                 return true;
             case MotionEvent.ACTION_SCROLL:
@@ -326,14 +324,22 @@ public static boolean sendMouseButtonUnconverted(int button, boolean status) {
         return true;
     }
 
-
-
-
-
     /** Called when the size need to be set at any point during the surface lifecycle **/
     public void refreshSize(){
-        windowWidth = Tools.getDisplayFriendlyRes(Tools.currentDisplayMetrics.widthPixels, mScaleFactor);
-        windowHeight = Tools.getDisplayFriendlyRes(Tools.currentDisplayMetrics.heightPixels, mScaleFactor);
+        refreshSize(false);
+    }
+
+    /** Same as refreshSize, but allows you to force an immediate size update **/
+    public void refreshSize(boolean immediate) {
+        if(isInLayout() && !immediate) {
+            post(this::refreshSize);
+            return;
+        }
+        // Use the width and height of the View instead of display dimensions to avoid
+        // getting squiched/stretched due to inconsistencies between the layout and
+        // screen dimensions.
+        windowWidth = Tools.getDisplayFriendlyRes(getWidth(), LauncherPreferences.PREF_SCALE_FACTOR);
+        windowHeight = Tools.getDisplayFriendlyRes(getHeight(), LauncherPreferences.PREF_SCALE_FACTOR);
         if(mSurface == null){
             Log.w("MGLSurface", "Attempt to refresh size on null surface");
             return;
@@ -355,8 +361,9 @@ public void refreshSize(){
     }
 
     private void realStart(Surface surface){
-        // Initial size set
-        refreshSize();
+        // Initial size set. Request immedate refresh, otherwise the initial width and height for the game
+        // may be broken/unknown.
+        refreshSize(true);
 
         //Load Minecraft options:
         MCOptionUtils.set("fullscreen", "off");
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java
index 581183654b..e8f280cfe7 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java
@@ -52,7 +52,7 @@ public void onCreate() {
 			}
 
 			FatalErrorActivity.showError(PojavApplication.this, crashFile.getAbsolutePath(), storagePermAllowed, th);
-			MainActivity.fullyExit();
+			Tools.fullyExit();
 		});
 		
 		try {
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java
index 431e8c1fbe..9bc091e470 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java
@@ -19,6 +19,8 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.Cursor;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -31,6 +33,9 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.View;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
 import android.view.WindowManager;
 import android.widget.EditText;
 import android.widget.TextView;
@@ -38,6 +43,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.core.app.NotificationManagerCompat;
@@ -506,10 +512,14 @@ public static DisplayMetrics getDisplayMetrics(Activity activity) {
         return displayMetrics;
     }
 
-    public static void setFullscreen(Activity activity, boolean fullscreen) {
+    @SuppressWarnings("deprecation")
+    private static void setFullscreenLegacy(Activity activity, boolean fullscreen) {
         final View decorView = activity.getWindow().getDecorView();
         View.OnSystemUiVisibilityChangeListener visibilityChangeListener = visibility -> {
-            if(fullscreen){
+            boolean multiWindowMode = SDK_INT >= 24 && activity.isInMultiWindowMode();
+            // When in multi-window mode, asking for fullscreen makes no sense (cause the launcher runs in a window)
+            // So, ignore the fullscreen setting when activity is in multi window mode
+            if(fullscreen && !multiWindowMode){
                 if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
                     decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                             | View.SYSTEM_UI_FLAG_FULLSCREEN
@@ -527,11 +537,67 @@ public static void setFullscreen(Activity activity, boolean fullscreen) {
         visibilityChangeListener.onSystemUiVisibilityChange(decorView.getSystemUiVisibility()); //call it once since the UI state may not change after the call, so the activity wont become fullscreen
     }
 
+    @RequiresApi(Build.VERSION_CODES.R)
+    private static void setFullscreenSdk30(Activity activity, boolean fullscreen) {
+        final Window window = activity.getWindow();
+        final View decorView = window.getDecorView();
+        final int insetControllerFlags = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars();
+        final View.OnApplyWindowInsetsListener windowInsetsListener = (view, windowInsets) ->{
+            WindowInsetsController windowInsetsController = decorView.getWindowInsetsController();
+            if(windowInsetsController == null) return windowInsets;
+            boolean multiWindowMode = activity.isInMultiWindowMode();
+            // Emulate the behaviour of the legacy function using the new flags
+            if(fullscreen && !multiWindowMode) {
+                windowInsetsController.hide(insetControllerFlags);
+                windowInsetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+                window.setDecorFitsSystemWindows(false);
+            }else {
+                windowInsetsController.show(insetControllerFlags);
+                // Both of the constants below have the exact same numerical value, but
+                // for some reason the one that works below Android S was removed
+                // from the acceptable constants for setSystemBarsBehaviour
+                if (SDK_INT >= Build.VERSION_CODES.S) {
+                    windowInsetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT);
+                }else {
+                    // noinspection WrongConstant
+                    windowInsetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE);
+                }
+                window.setDecorFitsSystemWindows(true);
+            }
+            return windowInsets;
+        };
+        decorView.setOnApplyWindowInsetsListener(windowInsetsListener);
+        windowInsetsListener.onApplyWindowInsets(decorView, null);
+    }
+
+    public static void setFullscreen(Activity activity, boolean fullscreen) {
+        if (SDK_INT >= Build.VERSION_CODES.R) {
+            setFullscreenSdk30(activity, fullscreen);
+        }else {
+            setFullscreenLegacy(activity, fullscreen);
+        }
+    }
+
     public static DisplayMetrics currentDisplayMetrics;
 
     public static void updateWindowSize(Activity activity) {
         currentDisplayMetrics = getDisplayMetrics(activity);
 
+        View dimensionView = activity.findViewById(R.id.dimension_tracker);
+
+        if(dimensionView != null) {
+            int width = dimensionView.getWidth();
+            int height = dimensionView.getHeight();
+            if(width != 0 && height != 0) {
+                Log.i("Tools", "Using dimension_tracker for display dimensions; W="+width+" H="+height);
+                CallbackBridge.physicalWidth = width;
+                CallbackBridge.physicalHeight = height;
+                return;
+            }else{
+                Log.e("Tools","Dimension tracker detected but dimensions out of date. Please check usage.", new Exception());
+            }
+        }
+
         CallbackBridge.physicalWidth = currentDisplayMetrics.widthPixels;
         CallbackBridge.physicalHeight = currentDisplayMetrics.heightPixels;
     }
@@ -606,7 +672,7 @@ private static void showError(final Context ctx, final int titleId, final String
                     .setPositiveButton(android.R.string.ok, (p1, p2) -> {
                         if(exitIfOk) {
                             if (ctx instanceof MainActivity) {
-                                MainActivity.fullyExit();
+                                fullyExit();
                             } else if (ctx instanceof Activity) {
                                 ((Activity) ctx).finish();
                             }
@@ -618,7 +684,7 @@ private static void showError(final Context ctx, final int titleId, final String
                         mgr.setPrimaryClip(ClipData.newPlainText("error", printToString(e)));
                         if(exitIfOk) {
                             if (ctx instanceof MainActivity) {
-                                MainActivity.fullyExit();
+                                fullyExit();
                             } else {
                                 ((Activity) ctx).finish();
                             }
@@ -890,6 +956,24 @@ public static void downloadFile(String urlInput, String nameOutput) throws IOExc
         File file = new File(nameOutput);
         DownloadUtils.downloadFile(urlInput, file);
     }
+
+    public static boolean isAndroid8OrHigher() {
+        return SDK_INT >= 26;
+    }
+
+    public static void fullyExit() {
+        android.os.Process.killProcess(android.os.Process.myPid());
+    }
+
+    public static void printLauncherInfo(String gameVersion, String javaArguments) {
+        Logger.appendToLog("Info: Launcher version: " + BuildConfig.VERSION_NAME);
+        Logger.appendToLog("Info: Architecture: " + Architecture.archAsString(DEVICE_ARCHITECTURE));
+        Logger.appendToLog("Info: Device model: " + Build.MANUFACTURER + " " +Build.MODEL);
+        Logger.appendToLog("Info: API version: " + SDK_INT);
+        Logger.appendToLog("Info: Selected Minecraft version: " + gameVersion);
+        Logger.appendToLog("Info: Custom Java arguments: \"" + javaArguments + "\"");
+    }
+
     public interface DownloaderFeedback {
         void updateProgress(int curr, int max);
     }
@@ -1243,4 +1327,22 @@ public static void releaseRenderersCache() {
         sCompatibleRenderers = null;
         System.gc();
     }
+
+    public static boolean deviceSupportsGyro(@NonNull Context context) {
+        return ((SensorManager)context.getSystemService(Context.SENSOR_SERVICE)).getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null;
+
+    }
+
+    public static void dialogForceClose(Context ctx) {
+        new android.app.AlertDialog.Builder(ctx)
+                .setMessage(R.string.mcn_exit_confirm)
+                .setNegativeButton(android.R.string.cancel, null)
+                .setPositiveButton(android.R.string.ok, (p1, p2) -> {
+                    try {
+                        Tools.fullyExit();
+                    } catch (Throwable th) {
+                        Log.w(Tools.APP_NAME, "Could not enable System.exit() method!", th);
+                    }
+                }).show();
+    }
 }
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelector.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelector.java
index dd614af164..f94b8351ad 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelector.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelector.java
@@ -5,76 +5,79 @@
 import android.graphics.Color;
 import android.text.Editable;
 import android.text.TextWatcher;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.EditText;
 
 import androidx.annotation.Nullable;
 
+import com.kdt.SideDialogView;
+
 import net.kdt.pojavlaunch.R;
 
-public class ColorSelector implements HueSelectionListener, RectangleSelectionListener, AlphaSelectionListener, TextWatcher{
+public class ColorSelector extends SideDialogView implements HueSelectionListener, RectangleSelectionListener, AlphaSelectionListener, TextWatcher{
     private static final int ALPHA_MASK = ~(0xFF << 24);
-    private final View mRootView;
-    private final HueView mHueView;
-    private final SVRectangleView mLuminosityIntensityView;
-    private final AlphaView mAlphaView;
-    private final ColorSideBySideView mColorView;
-    private final EditText mTextView;
+    private HueView mHueView;
+    private SVRectangleView mLuminosityIntensityView;
+    private AlphaView mAlphaView;
+    private ColorSideBySideView mColorView;
+    private EditText mTextView;
 
     private ColorSelectionListener mColorSelectionListener;
     private final float[] mHueTemplate = new float[] {0,1,1};
     private final float[] mHsvSelected = new float[] {360,1,1};
     private int mAlphaSelected = 0xff;
-    private final ColorStateList mTextColors;
+    private ColorStateList mTextColors;
     private boolean mWatch = true;
 
     private boolean mAlphaEnabled = true;
 
 
-    /**
-     * Creates a color selector dialog for this Context.
-     * @param context Context used for this ColorSelector dialog
-     * @param colorSelectionListener Color selection listener to which the events will be sent to. Can be null.
-     */
     public ColorSelector(Context context, ViewGroup parent, @Nullable ColorSelectionListener colorSelectionListener) {
+        super(context, parent, R.layout.dialog_color_selector);
+        mColorSelectionListener = colorSelectionListener;
+    }
 
-        mRootView = LayoutInflater.from(context).inflate(R.layout.dialog_color_selector,parent, false);
-        mHueView = mRootView.findViewById(R.id.color_selector_hue_view);
-        mLuminosityIntensityView = mRootView.findViewById(R.id.color_selector_rectangle_view);
-        mAlphaView = mRootView.findViewById(R.id.color_selector_alpha_view);
-        mColorView = mRootView.findViewById(R.id.color_selector_color_view);
-        mTextView = mRootView.findViewById(R.id.color_selector_hex_edit);
+    @Override
+    protected void onInflate() {
+        super.onInflate();
+        // Initialize the view contents
+        mHueView = mDialogContent.findViewById(R.id.color_selector_hue_view);
+        mLuminosityIntensityView = mDialogContent.findViewById(R.id.color_selector_rectangle_view);
+        mAlphaView = mDialogContent.findViewById(R.id.color_selector_alpha_view);
+        mColorView = mDialogContent.findViewById(R.id.color_selector_color_view);
+        mTextView = mDialogContent.findViewById(R.id.color_selector_hex_edit);
         runColor(Color.RED);
         mHueView.setHueSelectionListener(this);
         mLuminosityIntensityView.setRectSelectionListener(this);
         mAlphaView.setAlphaSelectionListener(this);
         mTextView.addTextChangedListener(this);
         mTextColors = mTextView.getTextColors();
-
-        mColorSelectionListener = colorSelectionListener;
-
-        parent.addView(mRootView);
-    }
-
-    /** @return The root view, mainly for position manipulation purposes */
-    public View getRootView(){
-        return mRootView;
+        mAlphaView.setVisibility(mAlphaEnabled ? View.VISIBLE : View.GONE);
+
+        // Set elevation to show above other side dialogs.
+        // Jank, should be done better
+        View contentParent = mDialogContent.findViewById(R.id.side_dialog_scrollview);
+        if(contentParent != null) {
+            ViewGroup dialogLayout = (ViewGroup) mDialogContent.getParent();
+            dialogLayout.setElevation(11);
+            dialogLayout.setTranslationZ(11);
+        }
     }
 
     /**
      * Shows the color selector with the default (red) color selected.
      */
-    public void show() {
-        show(Color.RED);
+    public void show(boolean fromRight) {
+        show(fromRight, Color.RED);
     }
 
     /**
      * Shows the color selector with the desired ARGB color selected
      * @param previousColor the desired ARGB color
      */
-    public void show(int previousColor) {
+    public void show(boolean fromRight, int previousColor) {
+        appear(fromRight);
         runColor(previousColor); // initialize
         dispatchColorChange(); // set the hex text
     }
@@ -157,8 +160,10 @@ public void setColorSelectionListener(ColorSelectionListener listener){
 
     public void setAlphaEnabled(boolean alphaEnabled){
         mAlphaEnabled = alphaEnabled;
-        mAlphaView.setVisibility(alphaEnabled ? View.VISIBLE : View.GONE);
-        mAlphaView.setAlpha(255);
+        if(mAlphaView != null) {
+            mAlphaView.setVisibility(alphaEnabled ? View.VISIBLE : View.GONE);
+            mAlphaView.setAlpha(255);
+        }
     }
 
     private void notifyColorSelector(int color){
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlLayout.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlLayout.java
index 19f5e3cd97..cc031aeff6 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlLayout.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlLayout.java
@@ -1,7 +1,6 @@
 package net.kdt.pojavlaunch.customcontrols;
 
 import static android.content.Context.INPUT_METHOD_SERVICE;
-import static net.kdt.pojavlaunch.MainActivity.mControlLayout;
 import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
 
 import static org.lwjgl.glfw.CallbackBridge.isGrabbing;
@@ -34,7 +33,7 @@
 import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
 import net.kdt.pojavlaunch.customcontrols.handleview.ActionRow;
 import net.kdt.pojavlaunch.customcontrols.handleview.ControlHandleView;
-import net.kdt.pojavlaunch.customcontrols.handleview.EditControlPopup;
+import net.kdt.pojavlaunch.customcontrols.handleview.EditControlSideDialog;
 import net.kdt.pojavlaunch.prefs.LauncherPreferences;
 
 import java.io.File;
@@ -54,7 +53,7 @@ public class ControlLayout extends FrameLayout {
 	private boolean mIsModified;
 	private boolean mControlVisible = false;
 
-	private EditControlPopup mControlPopup = null;
+	private EditControlSideDialog mControlDialog = null;
 	private ControlHandleView mHandleView;
 	private ControlButtonMenuListener mMenuListener;
 	public ActionRow mActionRow = null;
@@ -297,9 +296,9 @@ public void refreshControlButtonPositions(){
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
-        if(child instanceof ControlInterface && mControlPopup != null){
-            mControlPopup.disappearColor();
-            mControlPopup.disappear();
+        if(child instanceof ControlInterface && mControlDialog != null){
+			mControlDialog.disappearColor();
+            mControlDialog.disappear(false);
         }
     }
 
@@ -308,22 +307,23 @@ public void onViewRemoved(View child) {
 	 * to the button at hand.
 	 */
 	public void editControlButton(ControlInterface button){
-		if(mControlPopup == null){
+		if(mControlDialog == null){
 			// When the panel is null, it needs to inflate first.
 			// So inflate it, then process it on the next frame
-			mControlPopup = new EditControlPopup(getContext(), this);
+			mControlDialog = new EditControlSideDialog(getContext(), this);
 			post(() -> editControlButton(button));
 			return;
 		}
 
-		mControlPopup.internalChanges = true;
-		mControlPopup.setCurrentlyEditedButton(button);
-		button.loadEditValues(mControlPopup);
+		mControlDialog.internalChanges = true;
+		mControlDialog.setCurrentlyEditedButton(button);
 
-		mControlPopup.internalChanges = false;
+		mControlDialog.appear(button.getControlView().getX() + button.getControlView().getWidth()/2f < currentDisplayMetrics.widthPixels/2f);
+		button.loadEditValues(mControlDialog);
 
-		mControlPopup.appear(button.getControlView().getX() + button.getControlView().getWidth()/2f < currentDisplayMetrics.widthPixels/2f);
-		mControlPopup.disappearColor();
+		mControlDialog.internalChanges = false;
+
+		mControlDialog.disappearColor();
 
 		if(mHandleView == null){
 			mHandleView = new ControlHandleView(getContext());
@@ -336,8 +336,7 @@ public void editControlButton(ControlInterface button){
 
 	/** Swap the panel if the button position requires it */
 	public void adaptPanelPosition(){
-		if(mControlPopup != null)
-			mControlPopup.adaptPanelPosition();
+		if(mControlDialog != null) mControlDialog.adaptPanelPosition();
 	}
 
 
@@ -401,14 +400,14 @@ public void onTouch(View v, MotionEvent ev) {
 	@SuppressLint("ClickableViewAccessibility")
 	@Override
 	public boolean onTouchEvent(MotionEvent event) {
-		if (mModifiable && event.getActionMasked() != MotionEvent.ACTION_UP || mControlPopup == null)
+		if (mModifiable && event.getActionMasked() != MotionEvent.ACTION_UP || mControlDialog == null)
 			return true;
 
 		InputMethodManager imm = (InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE);
 
 		// When the input window cannot be hidden, it returns false
 		if(!imm.hideSoftInputFromWindow(getWindowToken(), 0)){
-			if(mControlPopup.disappearLayer()){
+			if(mControlDialog.disappearLayer()){
 				mActionRow.setFollowedButton(null);
 				mHandleView.hide();
 			}
@@ -421,9 +420,9 @@ public void removeEditWindow() {
 
 		// When the input window cannot be hidden, it returns false
 		imm.hideSoftInputFromWindow(getWindowToken(), 0);
-		if(mControlPopup != null) {
-			mControlPopup.disappearColor();
-			mControlPopup.disappear();
+		if(mControlDialog != null) {
+			mControlDialog.disappearColor();
+			mControlDialog.disappear(true);
 		}
 
 		if(mActionRow != null) mActionRow.setFollowedButton(null);
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java
index 888c85e5de..5fb9d687ba 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java
@@ -20,7 +20,7 @@
 import net.kdt.pojavlaunch.R;
 import net.kdt.pojavlaunch.customcontrols.ControlData;
 import net.kdt.pojavlaunch.customcontrols.ControlLayout;
-import net.kdt.pojavlaunch.customcontrols.handleview.EditControlPopup;
+import net.kdt.pojavlaunch.customcontrols.handleview.EditControlSideDialog;
 import net.kdt.pojavlaunch.prefs.LauncherPreferences;
 
 import org.lwjgl.glfw.CallbackBridge;
@@ -89,7 +89,7 @@ protected void onDraw(Canvas canvas) {
     }
 
 
-    public void loadEditValues(EditControlPopup editControlPopup){
+    public void loadEditValues(EditControlSideDialog editControlPopup){
         editControlPopup.loadValues(getProperties());
     }
 
@@ -207,7 +207,7 @@ private void sendSpecialKey(int keycode, boolean isDown){
                 break;
 
             case ControlData.SPECIALBTN_TOGGLECTRL:
-                if(isDown)MainActivity.mControlLayout.toggleControlVisible();
+                if(isDown)getControlLayoutParent().toggleControlVisible();
                 break;
 
             case ControlData.SPECIALBTN_VIRTUALMOUSE:
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlDrawer.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlDrawer.java
index cc022ea479..2c31604615 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlDrawer.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlDrawer.java
@@ -4,13 +4,12 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.Toast;
 
 import net.kdt.pojavlaunch.Tools;
 import net.kdt.pojavlaunch.customcontrols.ControlData;
 import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
 import net.kdt.pojavlaunch.customcontrols.ControlLayout;
-import net.kdt.pojavlaunch.customcontrols.handleview.EditControlPopup;
+import net.kdt.pojavlaunch.customcontrols.handleview.EditControlSideDialog;
 
 import java.util.ArrayList;
 
@@ -183,7 +182,7 @@ public ControlDrawerData getDrawerData() {
     }
 
     @Override
-    public void loadEditValues(EditControlPopup editControlPopup) {
+    public void loadEditValues(EditControlSideDialog editControlPopup) {
         editControlPopup.loadValues(drawerData);
     }
 
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlInterface.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlInterface.java
index a46380a950..569ddfefc9 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlInterface.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlInterface.java
@@ -20,7 +20,7 @@
 import net.kdt.pojavlaunch.Tools;
 import net.kdt.pojavlaunch.customcontrols.ControlData;
 import net.kdt.pojavlaunch.customcontrols.ControlLayout;
-import net.kdt.pojavlaunch.customcontrols.handleview.EditControlPopup;
+import net.kdt.pojavlaunch.customcontrols.handleview.EditControlSideDialog;
 
 import org.lwjgl.glfw.CallbackBridge;
 
@@ -61,7 +61,7 @@ default void setVisible(boolean isVisible) {
     /**
      * Load the values and hide non useful forms
      */
-    void loadEditValues(EditControlPopup editControlPopup);
+    void loadEditValues(EditControlSideDialog editControlDialog);
 
     @Override
     default void onGrabState(boolean isGrabbing) {
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlJoystick.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlJoystick.java
index e261d0873f..f9cbe21864 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlJoystick.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlJoystick.java
@@ -19,7 +19,7 @@
 import net.kdt.pojavlaunch.customcontrols.ControlJoystickData;
 import net.kdt.pojavlaunch.customcontrols.ControlLayout;
 import net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick;
-import net.kdt.pojavlaunch.customcontrols.handleview.EditControlPopup;
+import net.kdt.pojavlaunch.customcontrols.handleview.EditControlSideDialog;
 
 import org.lwjgl.glfw.CallbackBridge;
 
@@ -121,7 +121,7 @@ public void setBackground() {
     public void sendKeyPresses(boolean isDown) {/*STUB since non swipeable*/ }
 
     @Override
-    public void loadEditValues(EditControlPopup editControlPopup) {
+    public void loadEditValues(EditControlSideDialog editControlPopup) {
         editControlPopup.loadJoystickValues(mControlData);
     }
 
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlSubButton.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlSubButton.java
index 5a7242dd2d..8bdb4fea7a 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlSubButton.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlSubButton.java
@@ -2,13 +2,12 @@
 
 import android.annotation.SuppressLint;
 import android.view.MotionEvent;
-import android.view.View;
 import android.view.ViewGroup;
 
 import net.kdt.pojavlaunch.customcontrols.ControlData;
 import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
 import net.kdt.pojavlaunch.customcontrols.ControlLayout;
-import net.kdt.pojavlaunch.customcontrols.handleview.EditControlPopup;
+import net.kdt.pojavlaunch.customcontrols.handleview.EditControlSideDialog;
 
 @SuppressLint("ViewConstructor")
 public class ControlSubButton extends ControlButton {
@@ -91,7 +90,7 @@ public void snapAndAlign(float x, float y) {
     }
 
     @Override
-    public void loadEditValues(EditControlPopup editControlPopup) {
+    public void loadEditValues(EditControlSideDialog editControlPopup) {
         editControlPopup.loadSubButtonValues(getProperties(), parentDrawer.drawerData.orientation);
     }
 }
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlPopup.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlPopup.java
deleted file mode 100644
index 1e215484e3..0000000000
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlPopup.java
+++ /dev/null
@@ -1,653 +0,0 @@
-package net.kdt.pojavlaunch.customcontrols.handleview;
-
-
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
-
-import android.animation.ObjectAnimator;
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.graphics.Color;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.SeekBar;
-import android.widget.Spinner;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import androidx.constraintlayout.widget.ConstraintLayout;
-
-import com.kdt.DefocusableScrollView;
-
-import net.kdt.pojavlaunch.EfficientAndroidLWJGLKeycode;
-import net.kdt.pojavlaunch.R;
-import net.kdt.pojavlaunch.colorselector.ColorSelector;
-import net.kdt.pojavlaunch.customcontrols.ControlData;
-import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
-import net.kdt.pojavlaunch.customcontrols.ControlJoystickData;
-import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
-import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
-
-import java.util.List;
-
-/**
- * Class providing a sort of popup on top of a Layout, allowing to edit a given ControlButton
- */
-public class EditControlPopup {
-    protected final Spinner[] mKeycodeSpinners = new Spinner[4];
-    private final DefocusableScrollView mScrollView;
-    private final ColorSelector mColorSelector;
-
-    private final ObjectAnimator mEditPopupAnimator;
-    private final ObjectAnimator mColorEditorAnimator;
-    private final int mMargin;
-    public boolean internalChanges = false; // True when we programmatically change stuff.
-    private final View.OnLayoutChangeListener mLayoutChangedListener = new View.OnLayoutChangeListener() {
-        @Override
-        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
-            if (internalChanges) return;
-
-            internalChanges = true;
-            int width = (int) (safeParseFloat(mWidthEditText.getText().toString()));
-
-            if (width >= 0 && Math.abs(right - width) > 1) {
-                mWidthEditText.setText(String.valueOf(right - left));
-            }
-            int height = (int) (safeParseFloat(mHeightEditText.getText().toString()));
-            if (height >= 0 && Math.abs(bottom - height) > 1) {
-                mHeightEditText.setText(String.valueOf(bottom - top));
-            }
-
-            internalChanges = false;
-        }
-    };
-    protected EditText mNameEditText, mWidthEditText, mHeightEditText;
-    @SuppressLint("UseSwitchCompatOrMaterialCode")
-    protected Switch mToggleSwitch, mPassthroughSwitch, mSwipeableSwitch, mForwardLockSwitch, mAbsoluteTrackingSwitch;
-    protected Spinner mOrientationSpinner;
-    protected TextView[] mKeycodeTextviews = new TextView[4];
-    protected SeekBar mStrokeWidthSeekbar, mCornerRadiusSeekbar, mAlphaSeekbar;
-    protected TextView mStrokePercentTextView, mCornerRadiusPercentTextView, mAlphaPercentTextView;
-    protected TextView mSelectBackgroundColor, mSelectStrokeColor;
-    protected ArrayAdapter<String> mAdapter;
-    protected List<String> mSpecialArray;
-    protected CheckBox mDisplayInGameCheckbox, mDisplayInMenuCheckbox;
-    private ConstraintLayout mRootView;
-    private boolean mDisplaying = false;
-    private boolean mDisplayingColor = false;
-    private ControlInterface mCurrentlyEditedButton;
-    // Decorative textviews
-    private TextView mOrientationTextView, mMappingTextView, mNameTextView,
-            mCornerRadiusTextView, mVisibilityTextView, mSizeTextview, mSizeXTextView;
-
-
-    public EditControlPopup(Context context, ViewGroup parent) {
-        mScrollView = (DefocusableScrollView) LayoutInflater.from(context).inflate(R.layout.dialog_control_button_setting, parent, false);
-        parent.addView(mScrollView);
-
-        mMargin = context.getResources().getDimensionPixelOffset(R.dimen._20sdp);
-
-        mColorSelector = new ColorSelector(context, parent, null);
-        mColorSelector.getRootView().setElevation(11);
-        mColorSelector.getRootView().setTranslationZ(11);
-        mColorSelector.getRootView().setX(-context.getResources().getDimensionPixelOffset(R.dimen._280sdp));
-
-        mEditPopupAnimator = ObjectAnimator.ofFloat(mScrollView, "x", 0).setDuration(600);
-        mColorEditorAnimator = ObjectAnimator.ofFloat(mColorSelector.getRootView(), "x", 0).setDuration(600);
-        Interpolator decelerate = new AccelerateDecelerateInterpolator();
-        mEditPopupAnimator.setInterpolator(decelerate);
-        mColorEditorAnimator.setInterpolator(decelerate);
-
-        mScrollView.setElevation(10);
-        mScrollView.setTranslationZ(10);
-        mScrollView.setX(-context.getResources().getDimensionPixelOffset(R.dimen._280sdp));
-
-        bindLayout();
-        loadAdapter();
-
-        setupRealTimeListeners();
-    }
-
-    public static void setPercentageText(TextView textView, int progress) {
-        textView.setText(textView.getContext().getString(R.string.percent_format, progress));
-    }
-
-    /**
-     * Slide the layout into the visible screen area
-     */
-    public void appear(boolean fromRight) {
-        disappearColor(); // When someone jumps from a button to another
-
-        if (fromRight) {
-            if (!mDisplaying || !isAtRight()) {
-                mEditPopupAnimator.setFloatValues(currentDisplayMetrics.widthPixels, currentDisplayMetrics.widthPixels - mScrollView.getWidth() - mMargin);
-                mEditPopupAnimator.start();
-            }
-        } else {
-            if (!mDisplaying || isAtRight()) {
-                mEditPopupAnimator.setFloatValues(-mScrollView.getWidth(), mMargin);
-                mEditPopupAnimator.start();
-            }
-        }
-
-        mDisplaying = true;
-    }
-
-    /**
-     * Slide out the layout
-     */
-    public void disappear() {
-        if (!mDisplaying) return;
-
-        mDisplaying = false;
-        if (isAtRight())
-            mEditPopupAnimator.setFloatValues(currentDisplayMetrics.widthPixels - mScrollView.getWidth() - mMargin, currentDisplayMetrics.widthPixels);
-        else
-            mEditPopupAnimator.setFloatValues(mMargin, -mScrollView.getWidth());
-
-        mEditPopupAnimator.start();
-    }
-
-    /**
-     * Slide the layout into the visible screen area
-     */
-    public void appearColor(boolean fromRight, int color) {
-        if (fromRight) {
-            if (!mDisplayingColor || !isAtRight()) {
-                mColorEditorAnimator.setFloatValues(currentDisplayMetrics.widthPixels, currentDisplayMetrics.widthPixels - mScrollView.getWidth() - mMargin);
-                mColorEditorAnimator.start();
-            }
-        } else {
-            if (!mDisplayingColor || isAtRight()) {
-                mColorEditorAnimator.setFloatValues(-mScrollView.getWidth(), mMargin);
-                mColorEditorAnimator.start();
-            }
-        }
-
-        // Adjust the color selector to have the same size as the control settings
-        ViewGroup.LayoutParams params = mColorSelector.getRootView().getLayoutParams();
-        params.height = mScrollView.getHeight();
-        mColorSelector.getRootView().setLayoutParams(params);
-
-        mDisplayingColor = true;
-        mColorSelector.show(color == -1 ? Color.WHITE : color);
-    }
-
-    /**
-     * Slide out the layout
-     */
-    public void disappearColor() {
-        if (!mDisplayingColor) return;
-
-        mDisplayingColor = false;
-        if (isAtRight())
-            mColorEditorAnimator.setFloatValues(currentDisplayMetrics.widthPixels - mScrollView.getWidth() - mMargin, currentDisplayMetrics.widthPixels);
-        else
-            mColorEditorAnimator.setFloatValues(mMargin, -mScrollView.getWidth());
-
-        mColorEditorAnimator.start();
-    }
-
-    /**
-     * Slide out the first visible layer.
-     *
-     * @return True if the last layer is disappearing
-     */
-    public boolean disappearLayer() {
-        if (mDisplayingColor) {
-            disappearColor();
-            return false;
-        } else {
-            disappear();
-            return true;
-        }
-    }
-
-    /**
-     * Switch the panels position if needed
-     */
-    public void adaptPanelPosition() {
-        if (mDisplaying) {
-            boolean isAtRight = mCurrentlyEditedButton.getControlView().getX() + mCurrentlyEditedButton.getControlView().getWidth() / 2f < currentDisplayMetrics.widthPixels / 2f;
-            appear(isAtRight);
-        }
-    }
-
-    public void destroy() {
-        ((ViewGroup) mScrollView.getParent()).removeView(mColorSelector.getRootView());
-        ((ViewGroup) mScrollView.getParent()).removeView(mScrollView);
-    }
-
-    private void loadAdapter() {
-        //Initialize adapter for keycodes
-        mAdapter = new ArrayAdapter<>(mRootView.getContext(), R.layout.item_centered_textview);
-        mSpecialArray = ControlData.buildSpecialButtonArray();
-
-        mAdapter.addAll(mSpecialArray);
-        mAdapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName());
-        mAdapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
-
-        for (Spinner spinner : mKeycodeSpinners) {
-            spinner.setAdapter(mAdapter);
-        }
-
-        // Orientation spinner
-        ArrayAdapter<ControlDrawerData.Orientation> adapter = new ArrayAdapter<>(mScrollView.getContext(), android.R.layout.simple_spinner_item);
-        adapter.addAll(ControlDrawerData.getOrientations());
-        adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
-
-        mOrientationSpinner.setAdapter(adapter);
-    }
-
-    private void setDefaultVisibilitySetting() {
-        for (int i = 0; i < mRootView.getChildCount(); ++i) {
-            mRootView.getChildAt(i).setVisibility(VISIBLE);
-        }
-        for(Spinner s : mKeycodeSpinners) {
-            s.setVisibility(View.INVISIBLE);
-        }
-    }
-
-    private boolean isAtRight() {
-        return mScrollView.getX() > currentDisplayMetrics.widthPixels / 2f;
-    }
-
-    /* LOADING VALUES */
-
-    /**
-     * Load values for basic control data
-     */
-    public void loadValues(ControlData data) {
-        setDefaultVisibilitySetting();
-        mOrientationTextView.setVisibility(GONE);
-        mOrientationSpinner.setVisibility(GONE);
-        mForwardLockSwitch.setVisibility(GONE);
-        mAbsoluteTrackingSwitch.setVisibility(GONE);
-
-        mNameEditText.setText(data.name);
-        mWidthEditText.setText(String.valueOf(data.getWidth()));
-        mHeightEditText.setText(String.valueOf(data.getHeight()));
-
-        mAlphaSeekbar.setProgress((int) (data.opacity * 100));
-        mStrokeWidthSeekbar.setProgress((int) data.strokeWidth * 10);
-        mCornerRadiusSeekbar.setProgress((int) data.cornerRadius);
-
-        setPercentageText(mAlphaPercentTextView, (int) (data.opacity * 100));
-        setPercentageText(mStrokePercentTextView, (int) data.strokeWidth * 10);
-        setPercentageText(mCornerRadiusPercentTextView, (int) data.cornerRadius);
-
-        mToggleSwitch.setChecked(data.isToggle);
-        mPassthroughSwitch.setChecked(data.passThruEnabled);
-        mSwipeableSwitch.setChecked(data.isSwipeable);
-
-        mDisplayInGameCheckbox.setChecked(data.displayInGame);
-        mDisplayInMenuCheckbox.setChecked(data.displayInMenu);
-
-        for (int i = 0; i < data.keycodes.length; i++) {
-            if (data.keycodes[i] < 0) {
-                mKeycodeSpinners[i].setSelection(data.keycodes[i] + mSpecialArray.size());
-            } else {
-                mKeycodeSpinners[i].setSelection(EfficientAndroidLWJGLKeycode.getIndexByValue(data.keycodes[i]) + mSpecialArray.size());
-            }
-        }
-    }
-
-    /**
-     * Load values for extended control data
-     */
-    public void loadValues(ControlDrawerData data) {
-        loadValues(data.properties);
-
-        mOrientationSpinner.setSelection(
-                ControlDrawerData.orientationToInt(data.orientation));
-
-        mMappingTextView.setVisibility(GONE);
-        for (int i = 0; i < mKeycodeSpinners.length; i++) {
-            mKeycodeSpinners[i].setVisibility(GONE);
-            mKeycodeTextviews[i].setVisibility(GONE);
-        }
-
-        mOrientationTextView.setVisibility(VISIBLE);
-        mOrientationSpinner.setVisibility(VISIBLE);
-
-        mSwipeableSwitch.setVisibility(View.GONE);
-        mPassthroughSwitch.setVisibility(View.GONE);
-        mToggleSwitch.setVisibility(View.GONE);
-    }
-
-    /**
-     * Load values for the joystick
-     */
-    public void loadJoystickValues(ControlJoystickData data) {
-        loadValues(data);
-
-        mMappingTextView.setVisibility(GONE);
-        for (int i = 0; i < mKeycodeSpinners.length; i++) {
-            mKeycodeSpinners[i].setVisibility(GONE);
-            mKeycodeTextviews[i].setVisibility(GONE);
-        }
-
-        mNameTextView.setVisibility(GONE);
-        mNameEditText.setVisibility(GONE);
-
-        mCornerRadiusTextView.setVisibility(GONE);
-        mCornerRadiusSeekbar.setVisibility(GONE);
-        mCornerRadiusPercentTextView.setVisibility(GONE);
-
-        mSwipeableSwitch.setVisibility(View.GONE);
-        mPassthroughSwitch.setVisibility(View.GONE);
-        mToggleSwitch.setVisibility(View.GONE);
-
-        mForwardLockSwitch.setVisibility(VISIBLE);
-        mForwardLockSwitch.setChecked(data.forwardLock);
-
-        mAbsoluteTrackingSwitch.setVisibility(VISIBLE);
-        mAbsoluteTrackingSwitch.setChecked(data.absolute);
-    }
-
-    /**
-     * Load values for sub buttons
-     */
-    public void loadSubButtonValues(ControlData data, ControlDrawerData.Orientation drawerOrientation) {
-        loadValues(data);
-
-        // Size linked to the parent drawer depending on the drawer settings
-        if(drawerOrientation != ControlDrawerData.Orientation.FREE){
-            mSizeTextview.setVisibility(GONE);
-            mSizeXTextView.setVisibility(GONE);
-            mWidthEditText.setVisibility(GONE);
-            mHeightEditText.setVisibility(GONE);
-        }
-
-        // No conditional, already depends on the parent drawer visibility
-        mVisibilityTextView.setVisibility(GONE);
-        mDisplayInMenuCheckbox.setVisibility(GONE);
-        mDisplayInGameCheckbox.setVisibility(GONE);
-    }
-
-
-    private void bindLayout() {
-        mRootView = mScrollView.findViewById(R.id.edit_layout);
-        mNameEditText = mScrollView.findViewById(R.id.editName_editText);
-        mWidthEditText = mScrollView.findViewById(R.id.editSize_editTextX);
-        mHeightEditText = mScrollView.findViewById(R.id.editSize_editTextY);
-        mToggleSwitch = mScrollView.findViewById(R.id.checkboxToggle);
-        mPassthroughSwitch = mScrollView.findViewById(R.id.checkboxPassThrough);
-        mSwipeableSwitch = mScrollView.findViewById(R.id.checkboxSwipeable);
-        mForwardLockSwitch = mScrollView.findViewById(R.id.checkboxForwardLock);
-        mAbsoluteTrackingSwitch = mScrollView.findViewById(R.id.checkboxAbsoluteFingerTracking);
-        mKeycodeSpinners[0] = mScrollView.findViewById(R.id.editMapping_spinner_1);
-        mKeycodeSpinners[1] = mScrollView.findViewById(R.id.editMapping_spinner_2);
-        mKeycodeSpinners[2] = mScrollView.findViewById(R.id.editMapping_spinner_3);
-        mKeycodeSpinners[3] = mScrollView.findViewById(R.id.editMapping_spinner_4);
-        mKeycodeTextviews[0] = mScrollView.findViewById(R.id.mapping_1_textview);
-        mKeycodeTextviews[1] = mScrollView.findViewById(R.id.mapping_2_textview);
-        mKeycodeTextviews[2] = mScrollView.findViewById(R.id.mapping_3_textview);
-        mKeycodeTextviews[3] = mScrollView.findViewById(R.id.mapping_4_textview);
-        mOrientationSpinner = mScrollView.findViewById(R.id.editOrientation_spinner);
-        mStrokeWidthSeekbar = mScrollView.findViewById(R.id.editStrokeWidth_seekbar);
-        mCornerRadiusSeekbar = mScrollView.findViewById(R.id.editCornerRadius_seekbar);
-        mAlphaSeekbar = mScrollView.findViewById(R.id.editButtonOpacity_seekbar);
-        mSelectBackgroundColor = mScrollView.findViewById(R.id.editBackgroundColor_textView);
-        mSelectStrokeColor = mScrollView.findViewById(R.id.editStrokeColor_textView);
-        mStrokePercentTextView = mScrollView.findViewById(R.id.editStrokeWidth_textView_percent);
-        mAlphaPercentTextView = mScrollView.findViewById(R.id.editButtonOpacity_textView_percent);
-        mCornerRadiusPercentTextView = mScrollView.findViewById(R.id.editCornerRadius_textView_percent);
-        mDisplayInGameCheckbox = mScrollView.findViewById(R.id.visibility_game_checkbox);
-        mDisplayInMenuCheckbox = mScrollView.findViewById(R.id.visibility_menu_checkbox);
-
-        //Decorative stuff
-        mMappingTextView = mScrollView.findViewById(R.id.editMapping_textView);
-        mOrientationTextView = mScrollView.findViewById(R.id.editOrientation_textView);
-        mNameTextView = mScrollView.findViewById(R.id.editName_textView);
-        mCornerRadiusTextView = mScrollView.findViewById(R.id.editCornerRadius_textView);
-        mVisibilityTextView = mScrollView.findViewById(R.id.visibility_textview);
-        mSizeTextview = mScrollView.findViewById(R.id.editSize_textView);
-        mSizeXTextView = mScrollView.findViewById(R.id.editSize_x_textView);
-    }
-
-    /**
-     * A long function linking all the displayed data on the popup and,
-     * the currently edited mCurrentlyEditedButton
-     */
-    public void setupRealTimeListeners() {
-        mNameEditText.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-                if (internalChanges) return;
-
-                mCurrentlyEditedButton.getProperties().name = s.toString();
-
-                // Cheap and unoptimized, doesn't break the abstraction layer
-                mCurrentlyEditedButton.setProperties(mCurrentlyEditedButton.getProperties(), false);
-            }
-        });
-
-        mWidthEditText.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-                if (internalChanges) return;
-
-                float width = safeParseFloat(s.toString());
-                if (width >= 0) {
-                    mCurrentlyEditedButton.getProperties().setWidth(width);
-                    mCurrentlyEditedButton.updateProperties();
-                }
-            }
-        });
-
-        mHeightEditText.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-                if (internalChanges) return;
-
-                float height = safeParseFloat(s.toString());
-                if (height >= 0) {
-                    mCurrentlyEditedButton.getProperties().setHeight(height);
-                    mCurrentlyEditedButton.updateProperties();
-                }
-            }
-        });
-
-        mSwipeableSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            if (internalChanges) return;
-            mCurrentlyEditedButton.getProperties().isSwipeable = isChecked;
-        });
-        mToggleSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            if (internalChanges) return;
-            mCurrentlyEditedButton.getProperties().isToggle = isChecked;
-        });
-        mPassthroughSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            if (internalChanges) return;
-            mCurrentlyEditedButton.getProperties().passThruEnabled = isChecked;
-        });
-        mForwardLockSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            if (internalChanges) return;
-            if(mCurrentlyEditedButton.getProperties() instanceof ControlJoystickData){
-                ((ControlJoystickData) mCurrentlyEditedButton.getProperties()).forwardLock = isChecked;
-            }
-        });
-        mAbsoluteTrackingSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            if (internalChanges) return;
-            if(mCurrentlyEditedButton.getProperties() instanceof ControlJoystickData){
-                ((ControlJoystickData) mCurrentlyEditedButton.getProperties()).absolute = isChecked;
-            }
-        });
-
-        mAlphaSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                if (internalChanges) return;
-                mCurrentlyEditedButton.getProperties().opacity = mAlphaSeekbar.getProgress() / 100f;
-                mCurrentlyEditedButton.getControlView().setAlpha(mAlphaSeekbar.getProgress() / 100f);
-                setPercentageText(mAlphaPercentTextView, progress);
-            }
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {
-            }
-
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {
-            }
-        });
-
-        mStrokeWidthSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                if (internalChanges) return;
-                mCurrentlyEditedButton.getProperties().strokeWidth = mStrokeWidthSeekbar.getProgress() / 10F;
-                mCurrentlyEditedButton.setBackground();
-                setPercentageText(mStrokePercentTextView, progress);
-            }
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {
-            }
-
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {
-            }
-        });
-
-        mCornerRadiusSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                if (internalChanges) return;
-                mCurrentlyEditedButton.getProperties().cornerRadius = mCornerRadiusSeekbar.getProgress();
-                mCurrentlyEditedButton.setBackground();
-                setPercentageText(mCornerRadiusPercentTextView, progress);
-            }
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {
-            }
-
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {
-            }
-        });
-
-
-        for (int i = 0; i < mKeycodeSpinners.length; ++i) {
-            int finalI = i;
-            mKeycodeTextviews[i].setOnClickListener(v -> mKeycodeSpinners[finalI].performClick());
-
-            mKeycodeSpinners[i].setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
-                @Override
-                public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-                    // Side note, spinner listeners are fired later than all the other ones.
-                    // Meaning the internalChanges bool is useless here.
-                    if (position < mSpecialArray.size()) {
-                        mCurrentlyEditedButton.getProperties().keycodes[finalI] = mKeycodeSpinners[finalI].getSelectedItemPosition() - mSpecialArray.size();
-                    } else {
-                        mCurrentlyEditedButton.getProperties().keycodes[finalI] = EfficientAndroidLWJGLKeycode.getValueByIndex(mKeycodeSpinners[finalI].getSelectedItemPosition() - mSpecialArray.size());
-                    }
-                    mKeycodeTextviews[finalI].setText((String) mKeycodeSpinners[finalI].getSelectedItem());
-                }
-
-                @Override
-                public void onNothingSelected(AdapterView<?> parent) {
-                }
-            });
-        }
-
-
-        mOrientationSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-                // Side note, spinner listeners are fired later than all the other ones.
-                // Meaning the internalChanges bool is useless here.
-
-                if (mCurrentlyEditedButton instanceof ControlDrawer) {
-                    ((ControlDrawer) mCurrentlyEditedButton).drawerData.orientation = ControlDrawerData.intToOrientation(mOrientationSpinner.getSelectedItemPosition());
-                    ((ControlDrawer) mCurrentlyEditedButton).syncButtons();
-                }
-            }
-
-            @Override
-            public void onNothingSelected(AdapterView<?> parent) {
-            }
-        });
-
-        mDisplayInGameCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            if (internalChanges) return;
-            mCurrentlyEditedButton.getProperties().displayInGame = isChecked;
-        });
-
-        mDisplayInMenuCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            if (internalChanges) return;
-            mCurrentlyEditedButton.getProperties().displayInMenu = isChecked;
-        });
-
-        mSelectStrokeColor.setOnClickListener(v -> {
-            mColorSelector.setAlphaEnabled(false);
-            mColorSelector.setColorSelectionListener(color -> {
-                mCurrentlyEditedButton.getProperties().strokeColor = color;
-                mCurrentlyEditedButton.setBackground();
-            });
-            appearColor(isAtRight(), mCurrentlyEditedButton.getProperties().strokeColor);
-        });
-
-        mSelectBackgroundColor.setOnClickListener(v -> {
-            mColorSelector.setAlphaEnabled(true);
-            mColorSelector.setColorSelectionListener(color -> {
-                mCurrentlyEditedButton.getProperties().bgColor = color;
-                mCurrentlyEditedButton.setBackground();
-            });
-            appearColor(isAtRight(), mCurrentlyEditedButton.getProperties().bgColor);
-        });
-    }
-
-    private float safeParseFloat(String string) {
-        float out = -1; // -1
-        try {
-            out = Float.parseFloat(string);
-        } catch (NumberFormatException e) {
-            Log.e("EditControlPopup", e.toString());
-        }
-        return out;
-    }
-
-    public void setCurrentlyEditedButton(ControlInterface button) {
-        if (mCurrentlyEditedButton != null)
-            mCurrentlyEditedButton.getControlView().removeOnLayoutChangeListener(mLayoutChangedListener);
-        mCurrentlyEditedButton = button;
-        mCurrentlyEditedButton.getControlView().addOnLayoutChangeListener(mLayoutChangedListener);
-    }
-}
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlSideDialog.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlSideDialog.java
new file mode 100644
index 0000000000..14bf4dde4f
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlSideDialog.java
@@ -0,0 +1,486 @@
+package net.kdt.pojavlaunch.customcontrols.handleview;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Color;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.kdt.SideDialogView;
+
+import net.kdt.pojavlaunch.EfficientAndroidLWJGLKeycode;
+import net.kdt.pojavlaunch.R;
+import net.kdt.pojavlaunch.Tools;
+import net.kdt.pojavlaunch.colorselector.ColorSelector;
+import net.kdt.pojavlaunch.customcontrols.ControlData;
+import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
+import net.kdt.pojavlaunch.customcontrols.ControlJoystickData;
+import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
+import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
+import net.kdt.pojavlaunch.utils.interfaces.SimpleItemSelectedListener;
+import net.kdt.pojavlaunch.utils.interfaces.SimpleSeekBarListener;
+import net.kdt.pojavlaunch.utils.interfaces.SimpleTextWatcher;
+
+import java.util.List;
+
+public class EditControlSideDialog extends SideDialogView {
+
+    private final Spinner[] mKeycodeSpinners = new Spinner[4];
+    public boolean internalChanges = false; // True when we programmatically change stuff.
+    private final View.OnLayoutChangeListener mLayoutChangedListener = new View.OnLayoutChangeListener() {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            if (internalChanges) return;
+
+            internalChanges = true;
+            int width = (int) (safeParseFloat(mWidthEditText.getText().toString()));
+
+            if (width >= 0 && Math.abs(right - width) > 1) {
+                mWidthEditText.setText(String.valueOf(right - left));
+            }
+            int height = (int) (safeParseFloat(mHeightEditText.getText().toString()));
+            if (height >= 0 && Math.abs(bottom - height) > 1) {
+                mHeightEditText.setText(String.valueOf(bottom - top));
+            }
+
+            internalChanges = false;
+        }
+    };
+    private EditText mNameEditText, mWidthEditText, mHeightEditText;
+    @SuppressLint("UseSwitchCompatOrMaterialCode")
+    private Switch mToggleSwitch, mPassthroughSwitch, mSwipeableSwitch, mForwardLockSwitch, mAbsoluteTrackingSwitch;
+    private Spinner mOrientationSpinner;
+    private final TextView[] mKeycodeTextviews = new TextView[4];
+    private SeekBar mStrokeWidthSeekbar, mCornerRadiusSeekbar, mAlphaSeekbar;
+    private TextView mStrokePercentTextView, mCornerRadiusPercentTextView, mAlphaPercentTextView;
+    private TextView mSelectBackgroundColor, mSelectStrokeColor;
+    private ArrayAdapter<String> mAdapter;
+    private List<String> mSpecialArray;
+    private CheckBox mDisplayInGameCheckbox, mDisplayInMenuCheckbox;
+    private ControlInterface mCurrentlyEditedButton;
+    // Decorative textviews
+    private TextView mOrientationTextView, mMappingTextView, mNameTextView,
+            mCornerRadiusTextView, mVisibilityTextView, mSizeTextview, mSizeXTextView;
+
+    // Color selector related stuff
+    private ColorSelector mColorSelector;
+    private final ViewGroup mParent;
+
+    public EditControlSideDialog(Context context, ViewGroup parent) {
+        super(context, parent, R.layout.dialog_control_button_setting);
+        mParent = parent;
+    }
+
+    @Override
+    protected void onInflate() {
+        bindLayout();
+        buildColorSelector();
+        loadAdapter();
+        setupRealTimeListeners();
+    }
+
+    @Override
+    protected void onDestroy() {
+        mColorSelector.disappear(true);
+    }
+
+    private void buildColorSelector() {
+        mColorSelector = new ColorSelector(mParent.getContext(), mParent, null);
+    }
+
+    /**
+     * Slide the layout into the visible screen area
+     */
+    public void appearColor(boolean fromRight, int color) {
+        mColorSelector.show(fromRight, color == -1 ? Color.WHITE : color);
+    }
+
+    /**
+     * Slide out the layout
+     */
+    public void disappearColor() {
+        mColorSelector.disappear(false);
+    }
+
+    /**
+     * Slide out the first visible layer.
+     *
+     * @return True if the last layer is disappearing
+     */
+    public boolean disappearLayer() {
+        if (mColorSelector.isDisplaying()) {
+            disappearColor();
+            return false;
+        } else {
+            disappear(false);
+            return true;
+        }
+    }
+
+    /**
+     * Switch the panels position if needed
+     */
+    public void adaptPanelPosition() {
+        if (mDisplaying) {
+            boolean isAtRight = mCurrentlyEditedButton.getControlView().getX() + mCurrentlyEditedButton.getControlView().getWidth() / 2f < currentDisplayMetrics.widthPixels / 2f;
+            appear(isAtRight);
+            if (mColorSelector.isDisplaying()) {
+                Tools.runOnUiThread(() -> appearColor(isAtRight, mCurrentlyEditedButton.getProperties().bgColor));
+            }
+        }
+    }
+
+    public static void setPercentageText(TextView textView, int progress) {
+        textView.setText(textView.getContext().getString(R.string.percent_format, progress));
+    }
+
+    /* LOADING VALUES */
+
+    /**
+     * Load values for basic control data
+     */
+    public void loadValues(ControlData data) {
+        setDefaultVisibilitySetting();
+        mOrientationTextView.setVisibility(GONE);
+        mOrientationSpinner.setVisibility(GONE);
+        mForwardLockSwitch.setVisibility(GONE);
+        mAbsoluteTrackingSwitch.setVisibility(GONE);
+
+        mNameEditText.setText(data.name);
+        mWidthEditText.setText(String.valueOf(data.getWidth()));
+        mHeightEditText.setText(String.valueOf(data.getHeight()));
+
+        mAlphaSeekbar.setProgress((int) (data.opacity * 100));
+        mStrokeWidthSeekbar.setProgress((int) data.strokeWidth * 10);
+        mCornerRadiusSeekbar.setProgress((int) data.cornerRadius);
+
+        setPercentageText(mAlphaPercentTextView, (int) (data.opacity * 100));
+        setPercentageText(mStrokePercentTextView, (int) data.strokeWidth * 10);
+        setPercentageText(mCornerRadiusPercentTextView, (int) data.cornerRadius);
+
+        mToggleSwitch.setChecked(data.isToggle);
+        mPassthroughSwitch.setChecked(data.passThruEnabled);
+        mSwipeableSwitch.setChecked(data.isSwipeable);
+
+        mDisplayInGameCheckbox.setChecked(data.displayInGame);
+        mDisplayInMenuCheckbox.setChecked(data.displayInMenu);
+
+        for (int i = 0; i < data.keycodes.length; i++) {
+            if (data.keycodes[i] < 0) {
+                mKeycodeSpinners[i].setSelection(data.keycodes[i] + mSpecialArray.size());
+            } else {
+                mKeycodeSpinners[i].setSelection(EfficientAndroidLWJGLKeycode.getIndexByValue(data.keycodes[i]) + mSpecialArray.size());
+            }
+        }
+    }
+
+    /**
+     * Load values for extended control data
+     */
+    public void loadValues(ControlDrawerData data) {
+        loadValues(data.properties);
+
+        mOrientationSpinner.setSelection(
+                ControlDrawerData.orientationToInt(data.orientation));
+
+        mMappingTextView.setVisibility(GONE);
+        for (int i = 0; i < mKeycodeSpinners.length; i++) {
+            mKeycodeSpinners[i].setVisibility(GONE);
+            mKeycodeTextviews[i].setVisibility(GONE);
+        }
+
+        mOrientationTextView.setVisibility(VISIBLE);
+        mOrientationSpinner.setVisibility(VISIBLE);
+
+        mSwipeableSwitch.setVisibility(View.GONE);
+        mPassthroughSwitch.setVisibility(View.GONE);
+        mToggleSwitch.setVisibility(View.GONE);
+    }
+
+    /**
+     * Load values for the joystick
+     */
+    public void loadJoystickValues(ControlJoystickData data) {
+        loadValues(data);
+
+        mMappingTextView.setVisibility(GONE);
+        for (int i = 0; i < mKeycodeSpinners.length; i++) {
+            mKeycodeSpinners[i].setVisibility(GONE);
+            mKeycodeTextviews[i].setVisibility(GONE);
+        }
+
+        mNameTextView.setVisibility(GONE);
+        mNameEditText.setVisibility(GONE);
+
+        mCornerRadiusTextView.setVisibility(GONE);
+        mCornerRadiusSeekbar.setVisibility(GONE);
+        mCornerRadiusPercentTextView.setVisibility(GONE);
+
+        mSwipeableSwitch.setVisibility(View.GONE);
+        mPassthroughSwitch.setVisibility(View.GONE);
+        mToggleSwitch.setVisibility(View.GONE);
+
+        mForwardLockSwitch.setVisibility(VISIBLE);
+        mForwardLockSwitch.setChecked(data.forwardLock);
+
+        mAbsoluteTrackingSwitch.setVisibility(VISIBLE);
+        mAbsoluteTrackingSwitch.setChecked(data.absolute);
+    }
+
+    /**
+     * Load values for sub buttons
+     */
+    public void loadSubButtonValues(ControlData data, ControlDrawerData.Orientation drawerOrientation) {
+        loadValues(data);
+
+        // Size linked to the parent drawer depending on the drawer settings
+        if(drawerOrientation != ControlDrawerData.Orientation.FREE){
+            mSizeTextview.setVisibility(GONE);
+            mSizeXTextView.setVisibility(GONE);
+            mWidthEditText.setVisibility(GONE);
+            mHeightEditText.setVisibility(GONE);
+        }
+
+        // No conditional, already depends on the parent drawer visibility
+        mVisibilityTextView.setVisibility(GONE);
+        mDisplayInMenuCheckbox.setVisibility(GONE);
+        mDisplayInGameCheckbox.setVisibility(GONE);
+    }
+
+    private void loadAdapter() {
+        //Initialize adapter for keycodes
+        mAdapter = new ArrayAdapter<>(mDialogContent.getContext(), R.layout.item_centered_textview);
+        mSpecialArray = ControlData.buildSpecialButtonArray();
+
+        mAdapter.addAll(mSpecialArray);
+        mAdapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName());
+        mAdapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
+
+        for (Spinner spinner : mKeycodeSpinners) {
+            spinner.setAdapter(mAdapter);
+        }
+
+        // Orientation spinner
+        ArrayAdapter<ControlDrawerData.Orientation> adapter = new ArrayAdapter<>(mDialogContent.getContext(), android.R.layout.simple_spinner_item);
+        adapter.addAll(ControlDrawerData.getOrientations());
+        adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
+
+        mOrientationSpinner.setAdapter(adapter);
+    }
+
+    private void setDefaultVisibilitySetting() {
+        for (int i = 0; i < ((ViewGroup)mDialogContent).getChildCount(); ++i) {
+            ((ViewGroup)mDialogContent).getChildAt(i).setVisibility(VISIBLE);
+        }
+        for(Spinner s : mKeycodeSpinners) {
+            s.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    private void bindLayout() {
+        mNameEditText = mDialogContent.findViewById(R.id.editName_editText);
+        mWidthEditText = mDialogContent.findViewById(R.id.editSize_editTextX);
+        mHeightEditText = mDialogContent.findViewById(R.id.editSize_editTextY);
+        mToggleSwitch = mDialogContent.findViewById(R.id.checkboxToggle);
+        mPassthroughSwitch = mDialogContent.findViewById(R.id.checkboxPassThrough);
+        mSwipeableSwitch = mDialogContent.findViewById(R.id.checkboxSwipeable);
+        mForwardLockSwitch = mDialogContent.findViewById(R.id.checkboxForwardLock);
+        mAbsoluteTrackingSwitch = mDialogContent.findViewById(R.id.checkboxAbsoluteFingerTracking);
+        mKeycodeSpinners[0] = mDialogContent.findViewById(R.id.editMapping_spinner_1);
+        mKeycodeSpinners[1] = mDialogContent.findViewById(R.id.editMapping_spinner_2);
+        mKeycodeSpinners[2] = mDialogContent.findViewById(R.id.editMapping_spinner_3);
+        mKeycodeSpinners[3] = mDialogContent.findViewById(R.id.editMapping_spinner_4);
+        mKeycodeTextviews[0] = mDialogContent.findViewById(R.id.mapping_1_textview);
+        mKeycodeTextviews[1] = mDialogContent.findViewById(R.id.mapping_2_textview);
+        mKeycodeTextviews[2] = mDialogContent.findViewById(R.id.mapping_3_textview);
+        mKeycodeTextviews[3] = mDialogContent.findViewById(R.id.mapping_4_textview);
+        mOrientationSpinner = mDialogContent.findViewById(R.id.editOrientation_spinner);
+        mStrokeWidthSeekbar = mDialogContent.findViewById(R.id.editStrokeWidth_seekbar);
+        mCornerRadiusSeekbar = mDialogContent.findViewById(R.id.editCornerRadius_seekbar);
+        mAlphaSeekbar = mDialogContent.findViewById(R.id.editButtonOpacity_seekbar);
+        mSelectBackgroundColor = mDialogContent.findViewById(R.id.editBackgroundColor_textView);
+        mSelectStrokeColor = mDialogContent.findViewById(R.id.editStrokeColor_textView);
+        mStrokePercentTextView = mDialogContent.findViewById(R.id.editStrokeWidth_textView_percent);
+        mAlphaPercentTextView = mDialogContent.findViewById(R.id.editButtonOpacity_textView_percent);
+        mCornerRadiusPercentTextView = mDialogContent.findViewById(R.id.editCornerRadius_textView_percent);
+        mDisplayInGameCheckbox = mDialogContent.findViewById(R.id.visibility_game_checkbox);
+        mDisplayInMenuCheckbox = mDialogContent.findViewById(R.id.visibility_menu_checkbox);
+
+        //Decorative stuff
+        mMappingTextView = mDialogContent.findViewById(R.id.editMapping_textView);
+        mOrientationTextView = mDialogContent.findViewById(R.id.editOrientation_textView);
+        mNameTextView = mDialogContent.findViewById(R.id.editName_textView);
+        mCornerRadiusTextView = mDialogContent.findViewById(R.id.editCornerRadius_textView);
+        mVisibilityTextView = mDialogContent.findViewById(R.id.visibility_textview);
+        mSizeTextview = mDialogContent.findViewById(R.id.editSize_textView);
+        mSizeXTextView = mDialogContent.findViewById(R.id.editSize_x_textView);
+    }
+
+    /**
+     * A long function linking all the displayed data on the popup and,
+     * the currently edited mCurrentlyEditedButton
+     */
+    private void setupRealTimeListeners() {
+        mNameEditText.addTextChangedListener((SimpleTextWatcher) s -> {
+            if (internalChanges) return;
+
+            mCurrentlyEditedButton.getProperties().name = s.toString();
+
+            // Cheap and unoptimized, doesn't break the abstraction layer
+            mCurrentlyEditedButton.setProperties(mCurrentlyEditedButton.getProperties(), false);
+        });
+
+        mWidthEditText.addTextChangedListener((SimpleTextWatcher) s -> {
+            if (internalChanges) return;
+
+            float width = safeParseFloat(s.toString());
+            if (width >= 0) {
+                mCurrentlyEditedButton.getProperties().setWidth(width);
+                mCurrentlyEditedButton.updateProperties();
+            }
+        });
+
+        mHeightEditText.addTextChangedListener((SimpleTextWatcher) s -> {
+            if (internalChanges) return;
+
+            float height = safeParseFloat(s.toString());
+            if (height >= 0) {
+                mCurrentlyEditedButton.getProperties().setHeight(height);
+                mCurrentlyEditedButton.updateProperties();
+            }
+        });
+
+        mSwipeableSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (internalChanges) return;
+            mCurrentlyEditedButton.getProperties().isSwipeable = isChecked;
+        });
+        mToggleSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (internalChanges) return;
+            mCurrentlyEditedButton.getProperties().isToggle = isChecked;
+        });
+        mPassthroughSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (internalChanges) return;
+            mCurrentlyEditedButton.getProperties().passThruEnabled = isChecked;
+        });
+        mForwardLockSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (internalChanges) return;
+            if(mCurrentlyEditedButton.getProperties() instanceof ControlJoystickData){
+                ((ControlJoystickData) mCurrentlyEditedButton.getProperties()).forwardLock = isChecked;
+            }
+        });
+        mAbsoluteTrackingSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (internalChanges) return;
+            if(mCurrentlyEditedButton.getProperties() instanceof ControlJoystickData){
+                ((ControlJoystickData) mCurrentlyEditedButton.getProperties()).absolute = isChecked;
+            }
+        });
+
+        mAlphaSeekbar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> {
+            if (internalChanges) return;
+            mCurrentlyEditedButton.getProperties().opacity = mAlphaSeekbar.getProgress() / 100f;
+            mCurrentlyEditedButton.getControlView().setAlpha(mAlphaSeekbar.getProgress() / 100f);
+            setPercentageText(mAlphaPercentTextView, progress);
+        });
+
+        mStrokeWidthSeekbar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> {
+            if (internalChanges) return;
+            mCurrentlyEditedButton.getProperties().strokeWidth = mStrokeWidthSeekbar.getProgress() / 10F;
+            mCurrentlyEditedButton.setBackground();
+            setPercentageText(mStrokePercentTextView, progress);
+        });
+
+        mCornerRadiusSeekbar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> {
+            if (internalChanges) return;
+            mCurrentlyEditedButton.getProperties().cornerRadius = mCornerRadiusSeekbar.getProgress();
+            mCurrentlyEditedButton.setBackground();
+            setPercentageText(mCornerRadiusPercentTextView, progress);
+        });
+
+
+        for (int i = 0; i < mKeycodeSpinners.length; ++i) {
+            int finalI = i;
+            mKeycodeTextviews[i].setOnClickListener(v -> mKeycodeSpinners[finalI].performClick());
+
+            mKeycodeSpinners[i].setOnItemSelectedListener((SimpleItemSelectedListener) (parent, view, position, id) -> {
+                // Side note, spinner listeners are fired later than all the other ones.
+                // Meaning the internalChanges bool is useless here.
+                if (position < mSpecialArray.size()) {
+                    mCurrentlyEditedButton.getProperties().keycodes[finalI] = mKeycodeSpinners[finalI].getSelectedItemPosition() - mSpecialArray.size();
+                } else {
+                    mCurrentlyEditedButton.getProperties().keycodes[finalI] = EfficientAndroidLWJGLKeycode.getValueByIndex(mKeycodeSpinners[finalI].getSelectedItemPosition() - mSpecialArray.size());
+                }
+                mKeycodeTextviews[finalI].setText((String) mKeycodeSpinners[finalI].getSelectedItem());
+            });
+        }
+
+
+        mOrientationSpinner.setOnItemSelectedListener((SimpleItemSelectedListener) (parent, view, position, id) -> {
+            // Side note, spinner listeners are fired later than all the other ones.
+            // Meaning the internalChanges bool is useless here.
+
+            if (mCurrentlyEditedButton instanceof ControlDrawer) {
+                ((ControlDrawer) mCurrentlyEditedButton).drawerData.orientation = ControlDrawerData.intToOrientation(mOrientationSpinner.getSelectedItemPosition());
+                ((ControlDrawer) mCurrentlyEditedButton).syncButtons();
+            }
+        });
+
+        mDisplayInGameCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (internalChanges) return;
+            mCurrentlyEditedButton.getProperties().displayInGame = isChecked;
+        });
+
+        mDisplayInMenuCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (internalChanges) return;
+            mCurrentlyEditedButton.getProperties().displayInMenu = isChecked;
+        });
+
+        mSelectStrokeColor.setOnClickListener(v -> {
+            mColorSelector.setAlphaEnabled(false);
+            mColorSelector.setColorSelectionListener(color -> {
+                mCurrentlyEditedButton.getProperties().strokeColor = color;
+                mCurrentlyEditedButton.setBackground();
+            });
+            appearColor(isAtRight(), mCurrentlyEditedButton.getProperties().strokeColor);
+        });
+
+        mSelectBackgroundColor.setOnClickListener(v -> {
+            mColorSelector.setAlphaEnabled(true);
+            mColorSelector.setColorSelectionListener(color -> {
+                mCurrentlyEditedButton.getProperties().bgColor = color;
+                mCurrentlyEditedButton.setBackground();
+            });
+            appearColor(isAtRight(), mCurrentlyEditedButton.getProperties().bgColor);
+        });
+    }
+
+    private float safeParseFloat(String string) {
+        float out = -1; // -1
+        try {
+            out = Float.parseFloat(string);
+        } catch (NumberFormatException e) {
+            Log.e("EditControlPopup", e.toString());
+        }
+        return out;
+    }
+
+    public void setCurrentlyEditedButton(ControlInterface button) {
+        if (mCurrentlyEditedButton != null)
+            mCurrentlyEditedButton.getControlView().removeOnLayoutChangeListener(mLayoutChangedListener);
+        mCurrentlyEditedButton = button;
+        mCurrentlyEditedButton.getControlView().addOnLayoutChangeListener(mLayoutChangedListener);
+    }
+
+}
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java
index ec512ee0a0..38f6dd4ca7 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/AndroidPointerCapture.java
@@ -8,9 +8,9 @@
 
 import androidx.annotation.RequiresApi;
 
-import net.kdt.pojavlaunch.MainActivity;
 import net.kdt.pojavlaunch.MinecraftGLSurface;
 import net.kdt.pojavlaunch.Tools;
+import net.kdt.pojavlaunch.prefs.LauncherPreferences;
 
 import org.lwjgl.glfw.CallbackBridge;
 
@@ -19,7 +19,6 @@ public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChan
     private static final float TOUCHPAD_SCROLL_THRESHOLD = 1;
     private final AbstractTouchpad mTouchpad;
     private final View mHostView;
-    private final float mScaleFactor;
     private final float mMousePrescale = Tools.dpToPx(1);
     private final PointerTracker mPointerTracker = new PointerTracker();
     private final Scroller mScroller = new Scroller(TOUCHPAD_SCROLL_THRESHOLD);
@@ -28,8 +27,7 @@ public class AndroidPointerCapture implements ViewTreeObserver.OnWindowFocusChan
     private int mInputDeviceIdentifier;
     private boolean mDeviceSupportsRelativeAxis;
 
-    public AndroidPointerCapture(AbstractTouchpad touchpad, View hostView, float scaleFactor) {
-        this.mScaleFactor = scaleFactor;
+    public AndroidPointerCapture(AbstractTouchpad touchpad, View hostView) {
         this.mTouchpad = touchpad;
         this.mHostView = hostView;
         hostView.setOnCapturedPointerListener(this);
@@ -87,8 +85,8 @@ public boolean onCapturedPointer(View view, MotionEvent event) {
             }
         } else {
             // Position is updated by many events, hence it is send regardless of the event value
-            CallbackBridge.mouseX += (mVector[0] * mScaleFactor);
-            CallbackBridge.mouseY += (mVector[1] * mScaleFactor);
+            CallbackBridge.mouseX += (mVector[0] * LauncherPreferences.PREF_SCALE_FACTOR);
+            CallbackBridge.mouseY += (mVector[1] * LauncherPreferences.PREF_SCALE_FACTOR);
             CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
         }
 
@@ -130,7 +128,7 @@ private void reinitializeDeviceSpecificProperties(InputDevice inputDevice) {
 
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
-        if(hasFocus && MainActivity.isAndroid8OrHigher()) mHostView.requestPointerCapture();
+        if(hasFocus && Tools.isAndroid8OrHigher()) mHostView.requestPointerCapture();
     }
 
     public void detach() {
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/GyroControl.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/GyroControl.java
index 4ead091c13..ba0f7ec44b 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/GyroControl.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/GyroControl.java
@@ -21,6 +21,9 @@ public class GyroControl implements SensorEventListener, GrabListener {
     /* How much distance has to be moved before taking into account the gyro */
     private static final float SINGLE_AXIS_LOW_PASS_THRESHOLD = 1.13F;
     private static final float MULTI_AXIS_LOW_PASS_THRESHOLD = 1.3F;
+    // Warmup period of 2 since the first read from the sensor seems to produce a bogus value,
+    // which creates a far too large of a difference on the Y axis once actual sensor data comes in
+    private static final int ROTATION_VECTOR_WARMUP_PERIOD = 2;
 
     private final WindowManager mWindowManager;
     private int mSurfaceRotation;
@@ -28,7 +31,7 @@ public class GyroControl implements SensorEventListener, GrabListener {
     private final Sensor mSensor;
     private final OrientationCorrectionListener mCorrectionListener;
     private boolean mShouldHandleEvents;
-    private boolean mFirstPass;
+    private int mWarmup;
     private float xFactor; // -1 or 1 depending on device orientation
     private float yFactor;
     private boolean mSwapXY;
@@ -64,7 +67,7 @@ public GyroControl(Activity activity) {
 
     public void enable() {
         if(mSensor == null) return;
-        mFirstPass = true;
+        mWarmup = ROTATION_VECTOR_WARMUP_PERIOD;
         mSensorManager.registerListener(this, mSensor, 1000 * LauncherPreferences.PREF_GYRO_SAMPLE_RATE);
         mCorrectionListener.enable();
         mShouldHandleEvents = CallbackBridge.isGrabbing();
@@ -75,6 +78,7 @@ public void disable() {
         if(mSensor == null) return;
         mSensorManager.unregisterListener(this);
         mCorrectionListener.disable();
+        mStoredX = mStoredY = 0;
         resetDamper();
         CallbackBridge.removeGrabListener(this);
     }
@@ -87,8 +91,8 @@ public void onSensorChanged(SensorEvent sensorEvent) {
         SensorManager.getRotationMatrixFromVector(mCurrentRotation, sensorEvent.values);
 
 
-        if(mFirstPass){  // Setup initial position
-            mFirstPass = false;
+        if(mWarmup > 0){  // Setup initial position
+            mWarmup--;
             return;
         }
         SensorManager.getAngleChange(mAngleDifference, mCurrentRotation, mPreviousRotation);
@@ -161,7 +165,7 @@ public void onAccuracyChanged(Sensor sensor, int i) {}
 
     @Override
     public void onGrabState(boolean isGrabbing) {
-        mFirstPass = true;
+        mWarmup = ROTATION_VECTOR_WARMUP_PERIOD;
         mShouldHandleEvents = isGrabbing;
     }
 
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/HotbarView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/HotbarView.java
index cd2ff5d1ce..522b0fe5b8 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/HotbarView.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/HotbarView.java
@@ -26,7 +26,6 @@ public class HotbarView extends View implements MCOptionUtils.MCOptionListener,
             LwjglGlfwKeycode.GLFW_KEY_4, LwjglGlfwKeycode.GLFW_KEY_5,   LwjglGlfwKeycode.GLFW_KEY_6,
             LwjglGlfwKeycode.GLFW_KEY_7, LwjglGlfwKeycode.GLFW_KEY_8, LwjglGlfwKeycode.GLFW_KEY_9};
     private final DropGesture mDropGesture = new DropGesture(new Handler(Looper.getMainLooper()));
-    private final float mScaleFactor = LauncherPreferences.PREF_SCALE_FACTOR/100f;
     private int mWidth;
     private int mLastIndex;
     private int mGuiScale;
@@ -122,7 +121,14 @@ private boolean isLastEventInGesture(int actionMasked) {
     }
 
     private int mcScale(int input) {
-        return (int)((mGuiScale * input)/ mScaleFactor);
+        return (int)((mGuiScale * input) / LauncherPreferences.PREF_SCALE_FACTOR);
+    }
+
+    /** Forces the view to reposition itself. */
+    public void onResolutionChanged() {
+        if(getParent() == null) return;
+        mGuiScale = MCOptionUtils.getMcScale();
+        post(this::repositionView);
     }
 
     @Override
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java
index 16369048a2..21958d6afa 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGUIEventProcessor.java
@@ -19,12 +19,10 @@ public class InGUIEventProcessor implements TouchEventProcessor {
     private AbstractTouchpad mTouchpad;
     private boolean mIsMouseDown = false;
     private float mStartX, mStartY;
-    private final float mScaleFactor;
     private final Scroller mScroller = new Scroller(FINGER_SCROLL_THRESHOLD);
 
-    public InGUIEventProcessor(float scaleFactor) {
+    public InGUIEventProcessor() {
         mSingleTapDetector = new TapDetector(1, TapDetector.DETECTION_METHOD_BOTH);
-        mScaleFactor = scaleFactor;
     }
 
     @Override
@@ -91,7 +89,7 @@ public void setAbstractTouchpad(AbstractTouchpad touchpad) {
     }
 
     private void sendTouchCoordinates(float x, float y) {
-        CallbackBridge.sendCursorPos( x * mScaleFactor, y * mScaleFactor);
+        CallbackBridge.sendCursorPos( x * LauncherPreferences.PREF_SCALE_FACTOR, y * LauncherPreferences.PREF_SCALE_FACTOR);
     }
 
     private void enableMouse() {
@@ -105,8 +103,8 @@ private void disableMouse() {
     }
 
     private void setGestureStart(MotionEvent event) {
-        mStartX = event.getX() * mScaleFactor;
-        mStartY = event.getY() * mScaleFactor;
+        mStartX = event.getX() * LauncherPreferences.PREF_SCALE_FACTOR;
+        mStartY = event.getY() * LauncherPreferences.PREF_SCALE_FACTOR;
     }
 
     private void resetGesture() {
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGameEventProcessor.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGameEventProcessor.java
index a5838a458f..f4e5fe7639 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGameEventProcessor.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/InGameEventProcessor.java
@@ -2,6 +2,7 @@
 
 import android.os.Handler;
 import android.os.Looper;
+import android.util.Log;
 import android.view.MotionEvent;
 
 import net.kdt.pojavlaunch.prefs.LauncherPreferences;
@@ -32,8 +33,8 @@ public boolean processTouchEvent(MotionEvent motionEvent) {
             case MotionEvent.ACTION_MOVE:
                 mTracker.trackEvent(motionEvent);
                 float[] motionVector = mTracker.getMotionVector();
-                CallbackBridge.mouseX += motionVector[0] * mSensitivity;
-                CallbackBridge.mouseY += motionVector[1] * mSensitivity;
+                CallbackBridge.mouseX += (float) (motionVector[0] * mSensitivity);
+                CallbackBridge.mouseY += (float) (motionVector[1] * mSensitivity);
                 CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
                 if(LauncherPreferences.PREF_DISABLE_GESTURES) break;
                 checkGestures();
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 1feee772be..7a1f71d861 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
@@ -1,7 +1,6 @@
 package net.kdt.pojavlaunch.customcontrols.mouse;
 
 import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
-import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -29,8 +28,6 @@ public class Touchpad extends View implements GrabListener, AbstractTouchpad {
     /* Mouse pointer icon used by the touchpad */
     private Drawable mMousePointerDrawable;
     private float mMouseX, mMouseY;
-    /* Resolution scaler option, allow downsizing a window */
-    private final float mScaleFactor = DEFAULT_PREF.getInt("resolutionRatio",100)/100f;
     public Touchpad(@NonNull Context context) {
         this(context, null);
     }
@@ -68,7 +65,7 @@ public void placeMouseAt(float x, float y) {
     }
 
     private void sendMousePosition() {
-        CallbackBridge.sendCursorPos((mMouseX * mScaleFactor), (mMouseY * mScaleFactor));
+        CallbackBridge.sendCursorPos((mMouseX * LauncherPreferences.PREF_SCALE_FACTOR), (mMouseY * LauncherPreferences.PREF_SCALE_FACTOR));
     }
 
     private void updateMousePosition() {
@@ -93,8 +90,8 @@ private void init(){
         assert mMousePointerDrawable != null;
         mMousePointerDrawable.setBounds(
                 0, 0,
-                (int) (36 / 100f * LauncherPreferences.PREF_MOUSESCALE),
-                (int) (54 / 100f * LauncherPreferences.PREF_MOUSESCALE)
+                (int) (36 * LauncherPreferences.PREF_MOUSESCALE),
+                (int) (54 * LauncherPreferences.PREF_MOUSESCALE)
         );
         setFocusable(false);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
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 15d54a4842..5e73275034 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
@@ -36,7 +36,7 @@ public class LauncherPreferences {
 	public static boolean PREF_IGNORE_NOTCH = false;
 	public static int PREF_NOTCH_SIZE = 0;
 	public static float PREF_BUTTONSIZE = 100f;
-	public static float PREF_MOUSESCALE = 100f;
+	public static float PREF_MOUSESCALE = 1f;
 	public static int PREF_LONGPRESS_TRIGGER = 300;
 	public static String PREF_DEFAULTCTRL_PATH = Tools.CTRLDEF_FILE;
 	public static String PREF_CUSTOM_JAVA_ARGS;
@@ -53,7 +53,7 @@ public class LauncherPreferences {
     public static boolean PREF_ARC_CAPES = false;
     public static boolean PREF_USE_ALTERNATE_SURFACE = true;
     public static boolean PREF_JAVA_SANDBOX = true;
-    public static int PREF_SCALE_FACTOR = 100;
+    public static float PREF_SCALE_FACTOR = 1f;
     public static boolean PREF_ENABLE_GYRO = false;
     public static float PREF_GYRO_SENSITIVITY = 1f;
     public static int PREF_GYRO_SAMPLE_RATE = 16;
@@ -82,7 +82,7 @@ public static void loadPreferences(Context ctx) {
 
         PREF_RENDERER = DEFAULT_PREF.getString("renderer", "opengles2");
         PREF_BUTTONSIZE = DEFAULT_PREF.getInt("buttonscale", 100);
-        PREF_MOUSESCALE = DEFAULT_PREF.getInt("mousescale", 100);
+        PREF_MOUSESCALE = DEFAULT_PREF.getInt("mousescale", 100)/100f;
 		PREF_MOUSESPEED = ((float)DEFAULT_PREF.getInt("mousespeed",100))/100f;
 		PREF_HIDE_SIDEBAR = DEFAULT_PREF.getBoolean("hideSidebar", false);
 		PREF_IGNORE_NOTCH = DEFAULT_PREF.getBoolean("ignoreNotch", false);
@@ -103,7 +103,7 @@ public static void loadPreferences(Context ctx) {
         PREF_ARC_CAPES = DEFAULT_PREF.getBoolean("arc_capes",false);
         PREF_USE_ALTERNATE_SURFACE = DEFAULT_PREF.getBoolean("alternate_surface", false);
         PREF_JAVA_SANDBOX = DEFAULT_PREF.getBoolean("java_sandbox", true);
-        PREF_SCALE_FACTOR = DEFAULT_PREF.getInt("resolutionRatio", 100);
+        PREF_SCALE_FACTOR = DEFAULT_PREF.getInt("resolutionRatio", 100)/100f;
         PREF_ENABLE_GYRO = DEFAULT_PREF.getBoolean("enableGyro", false);
         PREF_GYRO_SENSITIVITY = ((float)DEFAULT_PREF.getInt("gyroSensitivity", 100))/100f;
         PREF_GYRO_SAMPLE_RATE = DEFAULT_PREF.getInt("gyroSampleRate", 16);
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
new file mode 100644
index 0000000000..47ebb7a809
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java
@@ -0,0 +1,259 @@
+package net.kdt.pojavlaunch.prefs;
+
+import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_DISABLE_GESTURES;
+import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_ENABLE_GYRO;
+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_LONGPRESS_TRIGGER;
+import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_MOUSESPEED;
+import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_SCALE_FACTOR;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.kdt.CustomSeekbar;
+
+import net.kdt.pojavlaunch.R;
+import net.kdt.pojavlaunch.Tools;
+import net.kdt.pojavlaunch.utils.interfaces.SimpleSeekBarListener;
+
+/**
+ * Side dialog for quick settings that you can change in game
+ * The implementation has to take action on some preference changes
+ */
+public abstract class QuickSettingSideDialog extends com.kdt.SideDialogView {
+
+    private SharedPreferences.Editor mEditor;
+    @SuppressLint("UseSwitchCompatOrMaterialCode")
+    private Switch mGyroSwitch, mGyroXSwitch, mGyroYSwitch, mGestureSwitch;
+    private CustomSeekbar mGyroSensitivityBar, mMouseSpeedBar, mGestureDelayBar, mResolutionBar;
+    private TextView mGyroSensitivityText, mGyroSensitivityDisplayText, mMouseSpeedText, mGestureDelayText, mGestureDelayDisplayText, mResolutionText;
+
+    private boolean mOriginalGyroEnabled, mOriginalGyroXEnabled, mOriginalGyroYEnabled, mOriginalGestureDisabled;
+    private float mOriginalGyroSensitivity, mOriginalMouseSpeed, mOriginalResolution;
+    private int mOriginalGestureDelay;
+
+    public QuickSettingSideDialog(Context context, ViewGroup parent) {
+        super(context, parent, R.layout.dialog_quick_setting);
+        setTitle(R.string.quick_setting_title);
+        setupCancelButton();
+    }
+
+    @Override
+    protected void onInflate() {
+        bindLayout();
+        Tools.runOnUiThread(() -> {
+            this.setupListeners();
+            this.updateGyroCompatibility();
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        removeListeners();
+    }
+
+    private void bindLayout() {
+        // Bind layout elements
+        mGyroSwitch = mDialogContent.findViewById(R.id.checkboxGyro);
+        mGyroXSwitch = mDialogContent.findViewById(R.id.checkboxGyroX);
+        mGyroYSwitch = mDialogContent.findViewById(R.id.checkboxGyroY);
+        mGestureSwitch = mDialogContent.findViewById(R.id.checkboxGesture);
+
+        mGyroSensitivityBar = mDialogContent.findViewById(R.id.editGyro_seekbar);
+        mMouseSpeedBar = mDialogContent.findViewById(R.id.editMouseSpeed_seekbar);
+        mGestureDelayBar = mDialogContent.findViewById(R.id.editGestureDelay_seekbar);
+        mResolutionBar = mDialogContent.findViewById(R.id.editResolution_seekbar);
+
+        mGyroSensitivityText = mDialogContent.findViewById(R.id.editGyro_textView_percent);
+        mGyroSensitivityDisplayText = mDialogContent.findViewById(R.id.editGyro_textView);
+        mMouseSpeedText = mDialogContent.findViewById(R.id.editMouseSpeed_textView_percent);
+        mGestureDelayText = mDialogContent.findViewById(R.id.editGestureDelay_textView_percent);
+        mGestureDelayDisplayText = mDialogContent.findViewById(R.id.editGestureDelay_textView);
+        mResolutionText = mDialogContent.findViewById(R.id.editResolution_textView_percent);
+    }
+
+    private void setupListeners() {
+        mEditor = LauncherPreferences.DEFAULT_PREF.edit();
+
+        mOriginalGyroEnabled = PREF_ENABLE_GYRO;
+        mOriginalGyroXEnabled = PREF_GYRO_INVERT_X;
+        mOriginalGyroYEnabled = PREF_GYRO_INVERT_Y;
+        mOriginalGestureDisabled = PREF_DISABLE_GESTURES;
+
+        mOriginalGyroSensitivity = PREF_GYRO_SENSITIVITY;
+        mOriginalMouseSpeed = PREF_MOUSESPEED;
+        mOriginalGestureDelay = PREF_LONGPRESS_TRIGGER;
+        mOriginalResolution = PREF_SCALE_FACTOR;
+
+        mGyroSwitch.setChecked(mOriginalGyroEnabled);
+        mGyroXSwitch.setChecked(mOriginalGyroXEnabled);
+        mGyroYSwitch.setChecked(mOriginalGyroYEnabled);
+        mGestureSwitch.setChecked(mOriginalGestureDisabled);
+
+        mGyroSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            PREF_ENABLE_GYRO = isChecked;
+            onGyroStateChanged();
+            updateGyroVisibility(isChecked);
+            mEditor.putBoolean("enableGyro", isChecked);
+        });
+
+        mGyroXSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            PREF_GYRO_INVERT_X = isChecked;
+            onGyroStateChanged();
+            mEditor.putBoolean("gyroInvertX", isChecked);
+        });
+
+        mGyroYSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            PREF_GYRO_INVERT_Y = isChecked;
+            onGyroStateChanged();
+            mEditor.putBoolean("gyroInvertY", isChecked);
+        });
+
+        mGestureSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            PREF_DISABLE_GESTURES = isChecked;
+            updateGestureVisibility(isChecked);
+            mEditor.putBoolean("disableGestures", isChecked);
+        });
+
+        mGyroSensitivityBar.setRange(25, 300);
+        mGyroSensitivityBar.setIncrement(5);
+        mGyroSensitivityBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> {
+            PREF_GYRO_SENSITIVITY = progress / 100f;
+            mEditor.putInt("gyroSensitivity", progress);
+            setSeekTextPercent(mGyroSensitivityText, progress);
+        });
+        mGyroSensitivityBar.setProgress((int) (mOriginalGyroSensitivity * 100f));
+        setSeekTextPercent(mGyroSensitivityText, mGyroSensitivityBar.getProgress());
+
+        mMouseSpeedBar.setRange(25, 300);
+        mMouseSpeedBar.setIncrement(5);
+        mMouseSpeedBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> {
+            PREF_MOUSESPEED = progress / 100f;
+            mEditor.putInt("mousespeed", progress);
+            setSeekTextPercent(mMouseSpeedText, progress);
+        });
+        mMouseSpeedBar.setProgress((int) (mOriginalMouseSpeed * 100f));
+        setSeekTextPercent(mMouseSpeedText, mMouseSpeedBar.getProgress());
+
+        mGestureDelayBar.setRange(100, 1000);
+        mGestureDelayBar.setIncrement(10);
+        mGestureDelayBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> {
+            PREF_LONGPRESS_TRIGGER = progress;
+            mEditor.putInt("timeLongPressTrigger", progress);
+            setSeekTextMillisecond(mGestureDelayText, progress);
+        });
+        mGestureDelayBar.setProgress(mOriginalGestureDelay);
+        setSeekTextMillisecond(mGestureDelayText, mGestureDelayBar.getProgress());
+
+        mResolutionBar.setRange(25, 100);
+        mResolutionBar.setIncrement(5);
+        mResolutionBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> {
+            PREF_SCALE_FACTOR = progress/100f;
+            mEditor.putInt("resolutionRatio", progress);
+            setSeekTextPercent(mResolutionText, progress);
+            onResolutionChanged();
+        });
+        mResolutionBar.setProgress((int) (mOriginalResolution * 100));
+        setSeekTextPercent(mResolutionText, mResolutionBar.getProgress());
+
+
+        updateGyroVisibility(mOriginalGyroEnabled);
+        updateGestureVisibility(mOriginalGestureDisabled);
+    }
+
+    private static void setSeekTextMillisecond(TextView target, int value) {
+        setSeekText(target, R.string.millisecond_format, value);
+    }
+
+    private static void setSeekTextPercent(TextView target, int value) {
+        setSeekText(target, R.string.percent_format, value);
+    }
+
+    private static void setSeekText(TextView target, int format, int value) {
+        target.setText(target.getContext().getString(format, value));
+    }
+
+    private void updateGyroVisibility(boolean isEnabled) {
+        int visibility = isEnabled ? View.VISIBLE : View.GONE;
+        mGyroXSwitch.setVisibility(visibility);
+        mGyroYSwitch.setVisibility(visibility);
+
+        mGyroSensitivityBar.setVisibility(visibility);
+        mGyroSensitivityText.setVisibility(visibility);
+        mGyroSensitivityDisplayText.setVisibility(visibility);
+    }
+
+    private void updateGyroCompatibility() {
+        boolean isGyroAvailable = Tools.deviceSupportsGyro(mDialogContent.getContext());
+        if (!isGyroAvailable) {
+            mGyroSwitch.setVisibility(View.GONE);
+            updateGestureVisibility(false);
+        }
+    }
+
+    private void updateGestureVisibility(boolean isDisabled) {
+        int visibility = isDisabled ? View.GONE : View.VISIBLE;
+        mGestureDelayBar.setVisibility(visibility);
+        mGestureDelayText.setVisibility(visibility);
+        mGestureDelayDisplayText.setVisibility(visibility);
+    }
+
+    private void removeListeners() {
+        mGyroSwitch.setOnCheckedChangeListener(null);
+        mGyroXSwitch.setOnCheckedChangeListener(null);
+        mGyroYSwitch.setOnCheckedChangeListener(null);
+        mGestureSwitch.setOnCheckedChangeListener(null);
+
+        mGyroSensitivityBar.setOnSeekBarChangeListener(null);
+        mMouseSpeedBar.setOnSeekBarChangeListener(null);
+        mGestureDelayBar.setOnSeekBarChangeListener(null);
+        mResolutionBar.setOnSeekBarChangeListener(null);
+    }
+
+    private void setupCancelButton() {
+        setStartButtonListener(android.R.string.cancel, v -> cancel());
+        setEndButtonListener(android.R.string.ok, v -> {
+            mEditor.apply();
+            disappear(true);
+        });
+    }
+
+    /** Resets all settings to their original values */
+    public void cancel() {
+        // Reset all settings if we were editing
+        if(isDisplaying()) {
+            PREF_ENABLE_GYRO = mOriginalGyroEnabled;
+            PREF_GYRO_INVERT_X = mOriginalGyroXEnabled;
+            PREF_GYRO_INVERT_Y = mOriginalGyroYEnabled;
+            PREF_DISABLE_GESTURES = mOriginalGestureDisabled;
+
+            PREF_GYRO_SENSITIVITY = mOriginalGyroSensitivity;
+            PREF_MOUSESPEED = mOriginalMouseSpeed;
+            PREF_LONGPRESS_TRIGGER = mOriginalGestureDelay;
+            PREF_SCALE_FACTOR = mOriginalResolution;
+
+            onGyroStateChanged();
+            onResolutionChanged();
+        }
+
+        disappear(true);
+    }
+
+    /** Called when the resolution is changed. Use {@link LauncherPreferences#PREF_SCALE_FACTOR} */
+    public abstract void onResolutionChanged();
+
+    /** Called when the gyro state is changed.
+     * Use {@link LauncherPreferences#PREF_ENABLE_GYRO}
+     * Use {@link LauncherPreferences#PREF_GYRO_INVERT_X}
+     * Use {@link LauncherPreferences#PREF_GYRO_INVERT_Y}
+     */
+    public abstract void onGyroStateChanged();
+
+}
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java
index a7400b8922..88ac51ffd8 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java
@@ -9,6 +9,7 @@
 import androidx.preference.PreferenceCategory;
 
 import net.kdt.pojavlaunch.R;
+import net.kdt.pojavlaunch.Tools;
 import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference;
 import net.kdt.pojavlaunch.prefs.LauncherPreferences;
 
@@ -19,7 +20,7 @@ public void onCreatePreferences(Bundle b, String str) {
         // Get values
         int longPressTrigger = LauncherPreferences.PREF_LONGPRESS_TRIGGER;
         int prefButtonSize = (int) LauncherPreferences.PREF_BUTTONSIZE;
-        int mouseScale = (int) LauncherPreferences.PREF_MOUSESCALE;
+        int mouseScale = (int) (LauncherPreferences.PREF_MOUSESCALE * 100);
         int gyroSampleRate = LauncherPreferences.PREF_GYRO_SAMPLE_RATE;
         float mouseSpeed = LauncherPreferences.PREF_MOUSESPEED;
         float gyroSpeed = LauncherPreferences.PREF_GYRO_SENSITIVITY;
@@ -62,7 +63,7 @@ public void onCreatePreferences(Bundle b, String str) {
 
         Context context = getContext();
         if(context != null) {
-            mGyroAvailable = ((SensorManager)context.getSystemService(Context.SENSOR_SERVICE)).getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null;
+            mGyroAvailable = Tools.deviceSupportsGyro(context);
         }
         PreferenceCategory gyroCategory =  requirePreference("gyroCategory",
                 PreferenceCategory.class);
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java
index fb85224ef2..da5cfa9c89 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java
@@ -323,7 +323,7 @@ public static void launchJavaVM(final AppCompatActivity activity, final Runtime
 
             LifecycleAwareAlertDialog.haltOnDialog(activity.getLifecycle(), activity, dialogCreator);
         }
-        MainActivity.fullyExit();
+        Tools.fullyExit();
     }
 
     /**
@@ -355,8 +355,8 @@ public static List<String> getJavaArgs(Context ctx, String runtimeHome, String u
                 //"-Dorg.lwjgl.util.DebugFunctions=true",
                 //"-Dorg.lwjgl.util.DebugLoader=true",
                 // GLFW Stub width height
-                "-Dglfwstub.windowWidth=" + Tools.getDisplayFriendlyRes(currentDisplayMetrics.widthPixels, LauncherPreferences.PREF_SCALE_FACTOR/100F),
-                "-Dglfwstub.windowHeight=" + Tools.getDisplayFriendlyRes(currentDisplayMetrics.heightPixels, LauncherPreferences.PREF_SCALE_FACTOR/100F),
+                "-Dglfwstub.windowWidth=" + Tools.getDisplayFriendlyRes(currentDisplayMetrics.widthPixels, LauncherPreferences.PREF_SCALE_FACTOR),
+                "-Dglfwstub.windowHeight=" + Tools.getDisplayFriendlyRes(currentDisplayMetrics.heightPixels, LauncherPreferences.PREF_SCALE_FACTOR),
                 "-Dglfwstub.initEgl=false",
                 "-Dext.net.resolvPath=" +resolvFile,
                 "-Dlog4j2.formatMsgNoLookups=true", //Log4j RCE mitigation
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/interfaces/SimpleItemSelectedListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/interfaces/SimpleItemSelectedListener.java
new file mode 100644
index 0000000000..3fe039f1a1
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/interfaces/SimpleItemSelectedListener.java
@@ -0,0 +1,15 @@
+package net.kdt.pojavlaunch.utils.interfaces;
+
+import android.view.View;
+import android.widget.AdapterView;
+
+/**
+ * Most interfaces implementations of {@link AdapterView.OnItemSelectedListener}
+ * only implement the {@link AdapterView.OnItemSelectedListener#onItemSelected(AdapterView, View, int, long)} onItemClick method.
+ * This class provides a default for other methods.
+ */
+public interface SimpleItemSelectedListener extends AdapterView.OnItemSelectedListener {
+    @Override
+    default void onNothingSelected(AdapterView<?> parent) {
+    }
+}
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/interfaces/SimpleSeekBarListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/interfaces/SimpleSeekBarListener.java
new file mode 100644
index 0000000000..ee88f5ceb9
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/interfaces/SimpleSeekBarListener.java
@@ -0,0 +1,17 @@
+package net.kdt.pojavlaunch.utils.interfaces;
+
+import android.widget.SeekBar;
+
+/**
+ * Most interfaces implementations of {@link SeekBar.OnSeekBarChangeListener}
+ * only implement the onProgressChanged method. This class provides a default for other methods.
+ */
+public interface SimpleSeekBarListener extends SeekBar.OnSeekBarChangeListener {
+    @Override
+    default void onStartTrackingTouch(android.widget.SeekBar seekBar) {
+    }
+
+    @Override
+    default void onStopTrackingTouch(android.widget.SeekBar seekBar) {
+    }
+}
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/interfaces/SimpleTextWatcher.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/interfaces/SimpleTextWatcher.java
new file mode 100644
index 0000000000..63c2f772a7
--- /dev/null
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/interfaces/SimpleTextWatcher.java
@@ -0,0 +1,16 @@
+package net.kdt.pojavlaunch.utils.interfaces;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+
+/**
+ * Most interfaces implementations of {@link TextWatcher} only implement the afterTextChanged method.
+ * This class provides a default for other methods.
+ */
+public interface SimpleTextWatcher extends TextWatcher {
+    @Override
+    default void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+    @Override
+    default void onTextChanged(CharSequence s, int start, int before, int count) {}
+}
diff --git a/app_pojavlauncher/src/main/jni/egl_bridge.c b/app_pojavlauncher/src/main/jni/egl_bridge.c
index 029bdeff3e..30b89631bb 100644
--- a/app_pojavlauncher/src/main/jni/egl_bridge.c
+++ b/app_pojavlauncher/src/main/jni/egl_bridge.c
@@ -212,11 +212,14 @@ int pojavInitOpenGL() {
     return 0;
 }
 
+extern void updateMonitorSize(int width, int height);
+
 EXTERNAL_API int pojavInit() {
     ANativeWindow_acquire(pojav_environ->pojavWindow);
     pojav_environ->savedWidth = ANativeWindow_getWidth(pojav_environ->pojavWindow);
     pojav_environ->savedHeight = ANativeWindow_getHeight(pojav_environ->pojavWindow);
     ANativeWindow_setBuffersGeometry(pojav_environ->pojavWindow,pojav_environ->savedWidth,pojav_environ->savedHeight,AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM);
+    updateMonitorSize(pojav_environ->savedWidth, pojav_environ->savedHeight);
     pojavInitOpenGL();
     return 1;
 }
diff --git a/app_pojavlauncher/src/main/jni/environ/environ.h b/app_pojavlauncher/src/main/jni/environ/environ.h
index 4d73f1fb34..fbfdb1b095 100644
--- a/app_pojavlauncher/src/main/jni/environ/environ.h
+++ b/app_pojavlauncher/src/main/jni/environ/environ.h
@@ -24,11 +24,9 @@ typedef void GLFW_invoke_Char_func(void* window, unsigned int codepoint);
 typedef void GLFW_invoke_CharMods_func(void* window, unsigned int codepoint, int mods);
 typedef void GLFW_invoke_CursorEnter_func(void* window, int entered);
 typedef void GLFW_invoke_CursorPos_func(void* window, double xpos, double ypos);
-typedef void GLFW_invoke_FramebufferSize_func(void* window, int width, int height);
 typedef void GLFW_invoke_Key_func(void* window, int key, int scancode, int action, int mods);
 typedef void GLFW_invoke_MouseButton_func(void* window, int button, int action, int mods);
 typedef void GLFW_invoke_Scroll_func(void* window, double xoffset, double yoffset);
-typedef void GLFW_invoke_WindowSize_func(void* window, int width, int height);
 
 struct pojav_environ_s {
     struct ANativeWindow* pojavWindow;
@@ -46,6 +44,7 @@ struct pojav_environ_s {
     jmethodID method_onGrabStateChanged;
     jmethodID method_glftSetWindowAttrib;
     jmethodID method_internalWindowSizeChanged;
+    jmethodID method_internalChangeMonitorSize;
     jclass bridgeClazz;
     jclass vmGlfwClass;
     jboolean isGrabbing;
@@ -57,6 +56,7 @@ struct pojav_environ_s {
     JNIEnv* dalvikJNIEnvPtr_ANDROID;
     long showingWindow;
     bool isInputReady, isCursorEntered, isUseStackQueueCall, shouldUpdateMouse;
+    bool shouldUpdateMonitorSize, monitorSizeConsumed;
     int savedWidth, savedHeight;
 #define ADD_CALLBACK_WWIN(NAME) \
     GLFW_invoke_##NAME##_func* GLFW_invoke_##NAME;
@@ -64,11 +64,9 @@ struct pojav_environ_s {
     ADD_CALLBACK_WWIN(CharMods);
     ADD_CALLBACK_WWIN(CursorEnter);
     ADD_CALLBACK_WWIN(CursorPos);
-    ADD_CALLBACK_WWIN(FramebufferSize);
     ADD_CALLBACK_WWIN(Key);
     ADD_CALLBACK_WWIN(MouseButton);
     ADD_CALLBACK_WWIN(Scroll);
-    ADD_CALLBACK_WWIN(WindowSize);
 
 #undef ADD_CALLBACK_WWIN
 };
diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c
index 5b50a82282..4bd94f6e1a 100644
--- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c
+++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c
@@ -26,11 +26,9 @@
 #define EVENT_TYPE_CHAR 1000
 #define EVENT_TYPE_CHAR_MODS 1001
 #define EVENT_TYPE_CURSOR_ENTER 1002
-#define EVENT_TYPE_FRAMEBUFFER_SIZE 1004
 #define EVENT_TYPE_KEY 1005
 #define EVENT_TYPE_MOUSE_BUTTON 1006
 #define EVENT_TYPE_SCROLL 1007
-#define EVENT_TYPE_WINDOW_SIZE 1008
 
 jint (*orig_ProcessImpl_forkAndExec)(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream);
 
@@ -52,7 +50,8 @@ jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) {
         (*vm)->GetEnv(vm, (void**) &pojav_environ->runtimeJNIEnvPtr_JRE, JNI_VERSION_1_4);
         pojav_environ->vmGlfwClass = (*pojav_environ->runtimeJNIEnvPtr_JRE)->NewGlobalRef(pojav_environ->runtimeJNIEnvPtr_JRE, (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "org/lwjgl/glfw/GLFW"));
         pojav_environ->method_glftSetWindowAttrib = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticMethodID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "glfwSetWindowAttrib", "(JII)V");
-        pojav_environ->method_internalWindowSizeChanged = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticMethodID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "internalWindowSizeChanged", "(JII)V");
+        pojav_environ->method_internalWindowSizeChanged = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticMethodID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "internalWindowSizeChanged", "(J)V");
+        pojav_environ->method_internalChangeMonitorSize = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticMethodID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "internalChangeMonitorSize", "(II)V");
         jfieldID field_keyDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticFieldID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "keyDownBuffer", "Ljava/nio/ByteBuffer;");
         jobject keyDownBufferJ = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticObjectField(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, field_keyDownBuffer);
         pojav_environ->keyDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetDirectBufferAddress(pojav_environ->runtimeJNIEnvPtr_JRE, keyDownBufferJ);
@@ -86,16 +85,17 @@ ADD_CALLBACK_WWIN(Char)
 ADD_CALLBACK_WWIN(CharMods)
 ADD_CALLBACK_WWIN(CursorEnter)
 ADD_CALLBACK_WWIN(CursorPos)
-ADD_CALLBACK_WWIN(FramebufferSize)
 ADD_CALLBACK_WWIN(Key)
 ADD_CALLBACK_WWIN(MouseButton)
 ADD_CALLBACK_WWIN(Scroll)
-ADD_CALLBACK_WWIN(WindowSize)
 
 #undef ADD_CALLBACK_WWIN
 
-void handleFramebufferSizeJava(long window, int w, int h) {
-    (*pojav_environ->runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, pojav_environ->method_internalWindowSizeChanged, (long)window, w, h);
+void updateMonitorSize(int width, int height) {
+    (*pojav_environ->runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, pojav_environ->method_internalChangeMonitorSize, width, height);
+}
+void updateWindowSize(void* window) {
+    (*pojav_environ->runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, pojav_environ->method_internalWindowSizeChanged, (jlong)window);
 }
 
 void pojavPumpEvents(void* window) {
@@ -103,6 +103,9 @@ void pojavPumpEvents(void* window) {
         pojav_environ->GLFW_invoke_CursorPos(window, floor(pojav_environ->cursorX),
                                              floor(pojav_environ->cursorY));
     }
+    if(pojav_environ->shouldUpdateMonitorSize) {
+        updateWindowSize(window);
+    }
 
     size_t index = pojav_environ->outEventIndex;
     size_t targetIndex = pojav_environ->outTargetIndex;
@@ -125,14 +128,6 @@ void pojavPumpEvents(void* window) {
             case EVENT_TYPE_SCROLL:
                 if(pojav_environ->GLFW_invoke_Scroll) pojav_environ->GLFW_invoke_Scroll(window, event.i1, event.i2);
                 break;
-            case EVENT_TYPE_FRAMEBUFFER_SIZE:
-                handleFramebufferSizeJava(pojav_environ->showingWindow, event.i1, event.i2);
-                if(pojav_environ->GLFW_invoke_FramebufferSize) pojav_environ->GLFW_invoke_FramebufferSize(window, event.i1, event.i2);
-                break;
-            case EVENT_TYPE_WINDOW_SIZE:
-                handleFramebufferSizeJava(pojav_environ->showingWindow, event.i1, event.i2);
-                if(pojav_environ->GLFW_invoke_WindowSize) pojav_environ->GLFW_invoke_WindowSize(window, event.i1, event.i2);
-                break;
         }
 
         index++;
@@ -162,6 +157,12 @@ void pojavStartPumping() {
         pojav_environ->cLastY = pojav_environ->cursorY;
         pojav_environ->shouldUpdateMouse = true;
     }
+    if(pojav_environ->shouldUpdateMonitorSize) {
+        // Perform a monitor size update here to avoid doing it on every single window
+        updateMonitorSize(pojav_environ->savedWidth, pojav_environ->savedHeight);
+        // Mark the monitor size as consumed (since GLFW was made aware of it)
+        pojav_environ->monitorSizeConsumed = true;
+    }
 }
 
 /** Prepare the library for the next round of new events */
@@ -170,8 +171,16 @@ void pojavStopPumping() {
 
     // New events may have arrived while pumping, so remove only the difference before the start and end of execution
     atomic_fetch_sub_explicit(&pojav_environ->eventCounter, pojav_environ->inEventCount, memory_order_acquire);
-    // Make sure the next frame won't send mouse updates if it's unnecessary
+    // Make sure the next frame won't send mouse or monitor updates if it's unnecessary
     pojav_environ->shouldUpdateMouse = false;
+    // Only reset the update flag if the monitor size was consumed by pojavStartPumping. This
+    // will delay the update to next frame if it had occured between pojavStartPumping and pojavStopPumping,
+    // but it's better than not having it apply at all
+    if(pojav_environ->shouldUpdateMonitorSize && pojav_environ->monitorSizeConsumed) {
+        pojav_environ->shouldUpdateMonitorSize = false;
+        pojav_environ->monitorSizeConsumed = false;
+    }
+
 }
 
 JNIEXPORT void JNICALL
@@ -502,23 +511,15 @@ void noncritical_send_mouse_button(__attribute__((unused)) JNIEnv* env, __attrib
 void critical_send_screen_size(jint width, jint height) {
     pojav_environ->savedWidth = width;
     pojav_environ->savedHeight = height;
-    if (pojav_environ->isInputReady) {
-        if (pojav_environ->GLFW_invoke_FramebufferSize) {
-            if (pojav_environ->isUseStackQueueCall) {
-                sendData(EVENT_TYPE_FRAMEBUFFER_SIZE, width, height, 0, 0);
-            } else {
-                pojav_environ->GLFW_invoke_FramebufferSize((void*) pojav_environ->showingWindow, width, height);
-            }
-        }
-
-        if (pojav_environ->GLFW_invoke_WindowSize) {
-            if (pojav_environ->isUseStackQueueCall) {
-                sendData(EVENT_TYPE_WINDOW_SIZE, width, height, 0, 0);
-            } else {
-                pojav_environ->GLFW_invoke_WindowSize((void*) pojav_environ->showingWindow, width, height);
-            }
-        }
-    }
+    // Even if there was call to pojavStartPumping that consumed the size, this call
+    // might happen right after it (or right before pojavStopPumping)
+    // So unmark the size as "consumed"
+    pojav_environ->monitorSizeConsumed = false;
+    pojav_environ->shouldUpdateMonitorSize = true;
+    // Don't use the direct updates  for screen dimensions.
+    // This is done to ensure that we have predictable conditions to correctly call
+    // updateMonitorSize() and updateWindowSize() while on the render thread with an attached
+    // JNIEnv.
 }
 
 void noncritical_send_screen_size(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint width, jint height) {
@@ -541,7 +542,7 @@ void noncritical_send_scroll(__attribute__((unused)) JNIEnv* env, __attribute__(
 
 
 JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_nglfwSetShowingWindow(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jlong window) {
-    pojav_environ->showingWindow = (long) window;
+    pojav_environ->showingWindow = (jlong) window;
 }
 
 JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetWindowAttrib(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint attrib, jint value) {
diff --git a/app_pojavlauncher/src/main/res/layout/activity_basemain.xml b/app_pojavlauncher/src/main/res/layout/activity_basemain.xml
index c97c03d065..a7a95d639a 100644
--- a/app_pojavlauncher/src/main/res/layout/activity_basemain.xml
+++ b/app_pojavlauncher/src/main/res/layout/activity_basemain.xml
@@ -20,6 +20,8 @@
 			android:layout_width="match_parent"
 			android:layout_height="match_parent">
 
+			<View style="@style/DimensionTracker"/>
+
 			<net.kdt.pojavlaunch.MinecraftGLSurface
 				android:id="@+id/main_game_render_view"
 				android:layout_width="fill_parent"
@@ -54,6 +56,7 @@
 				android:elevation="10dp"
 				android:layout_gravity="center_horizontal"/>
 			<net.kdt.pojavlaunch.customcontrols.mouse.HotbarView
+				android:id="@+id/hotbar_view"
 				android:layout_width="0px"
 				android:layout_height="0px"/>
 
@@ -74,7 +77,6 @@
 		android:layout_gravity="right"
 		android:background="?attr/colorBackgroundFloating"
 		android:fitsSystemWindows="false"
-		app:menu="@menu/menu_runopt"
 		android:id="@+id/main_navigation_view"/>
 
 </androidx.drawerlayout.widget.DrawerLayout>
diff --git a/app_pojavlauncher/src/main/res/layout/dialog_control_button_setting.xml b/app_pojavlauncher/src/main/res/layout/dialog_control_button_setting.xml
index 08508c24a4..048d12c3bd 100644
--- a/app_pojavlauncher/src/main/res/layout/dialog_control_button_setting.xml
+++ b/app_pojavlauncher/src/main/res/layout/dialog_control_button_setting.xml
@@ -1,507 +1,500 @@
 <?xml version="1.0" encoding="utf-8"?>
-<com.kdt.DefocusableScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="@dimen/_280sdp"
+    android:id="@+id/edit_layout"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_marginVertical="@dimen/padding_heavy"
-    android:background="@drawable/background_control_editor"
-    android:layout_gravity="center_vertical"
-    >
+    android:paddingHorizontal="@dimen/padding_moderate"
+    android:paddingVertical="@dimen/padding_moderate"
+    app:layout_optimizationLevel="standard|dimensions|chains">
+
+
+    <!-- EDIT NAME SECTION -->
+    <TextView
+        android:id="@+id/editName_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/global_name"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <EditText
+        android:id="@+id/editName_editText"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:hint="Button Name"
+        android:imeOptions="flagNoExtractUi"
+        android:maxLines="1"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editName_textView" />
+
+    <!--  SIZE SECTION -->
+    <TextView
+        android:id="@+id/editSize_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/customctrl_size"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/editName_editText"
+        tools:visibility="visible" />
+
+    <EditText
+        android:id="@+id/editSize_editTextX"
+        android:layout_width="@dimen/_134sdp"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:imeOptions="flagNoExtractUi"
+        android:inputType="numberDecimal"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editSize_textView" />
+
+    <EditText
+        android:id="@+id/editSize_editTextY"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:imeOptions="flagNoExtractUi"
+        android:inputType="numberDecimal"
+
+        app:layout_constraintBottom_toBottomOf="@+id/editSize_editTextX"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/editSize_editTextX"
+        app:layout_constraintTop_toTopOf="@+id/editSize_editTextX" />
+
+    <TextView
+        android:id="@+id/editSize_x_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:text="x"
+
+        app:layout_constraintBottom_toBottomOf="@+id/editSize_editTextX"
+        app:layout_constraintEnd_toStartOf="@id/editSize_editTextY"
+        app:layout_constraintStart_toEndOf="@id/editSize_editTextX"
+        app:layout_constraintTop_toTopOf="@+id/editSize_editTextX" />
+
+    <!-- MAPPING SECTION -->
+    <TextView
+        android:id="@+id/editMapping_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+
+        android:paddingEnd="5dp"
+        android:text="@string/customctrl_mapping"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editSize_editTextX" />
+
+    <!--
+        Spinners are hidden to handle to workaround the horizontal offset.
+        The horizontal offset was needed to display the spinner list at the same place.
+        Sadly it is broken since Android Jelly Bean and never scheduled to be fixed
+    -->
+    <Spinner
+        android:id="@+id/editMapping_spinner_1"
+        android:layout_width="0dp"
+        android:layout_height="30dp"
+        android:dropDownWidth="match_parent"
+        android:dropDownVerticalOffset="30dp"
+        android:gravity="center"
+        android:visibility="invisible"
+        android:background="@android:color/transparent"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editMapping_textView" />
+
+
+    <Spinner
+        android:id="@+id/editMapping_spinner_2"
+        android:layout_width="0dp"
+        android:layout_height="30dp"
+        android:dropDownWidth="match_parent"
+        android:dropDownHorizontalOffset="30dp"
+        android:dropDownVerticalOffset="30dp"
+        android:gravity="center"
+        android:visibility="invisible"
+        android:background="@android:color/transparent"
+
+        app:layout_constraintTop_toBottomOf="@+id/editMapping_textView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <Spinner
+        android:id="@+id/editMapping_spinner_3"
+        android:layout_width="0dp"
+        android:layout_height="30dp"
+        android:dropDownWidth="match_parent"
+        android:dropDownVerticalOffset="30dp"
+        android:gravity="center"
+        android:visibility="invisible"
+        android:background="@android:color/transparent"
+
+        app:layout_constraintTop_toTopOf="@+id/mapping_3_textview"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+
+    <Spinner
+        android:id="@+id/editMapping_spinner_4"
+        android:layout_width="0dp"
+        android:layout_height="30dp"
+        android:dropDownWidth="match_parent"
+        android:dropDownVerticalOffset="30dp"
+        android:visibility="invisible"
+        android:background="@android:color/transparent"
+
+        app:layout_constraintTop_toTopOf="@+id/mapping_3_textview"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <!-- Clickable textview linked to the spinners
+        Spinners are hidden at the same place because the dropdown offset doesn't work
+        Do those textview acts as the spinner idle state
+    -->
+
+    <TextView
+        android:id="@+id/mapping_1_textview"
+        style="?android:attr/spinnerItemStyle"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_30sdp"
+        android:ellipsize="marquee"
+        android:gravity="center"
+        android:singleLine="true"
+        android:textAlignment="inherit"
+
+        app:layout_constraintEnd_toStartOf="@id/mapping_2_textview"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/editMapping_textView"
+
+        tools:text="HELLO" />
+
+    <TextView
+        android:id="@+id/editMapping_plus_1"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:text="+"
+
+        app:layout_constraintBottom_toBottomOf="@+id/mapping_1_textview"
+        app:layout_constraintEnd_toEndOf="@+id/mapping_1_textview"
+        app:layout_constraintStart_toEndOf="@id/mapping_1_textview"
+        app:layout_constraintTop_toTopOf="@+id/mapping_1_textview" />
+
+    <TextView
+        android:id="@+id/mapping_2_textview"
+        style="?android:attr/spinnerItemStyle"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_30sdp"
+        android:ellipsize="marquee"
+        android:gravity="center"
+        android:singleLine="true"
+        android:textAlignment="inherit"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/mapping_1_textview"
+
+        app:layout_constraintTop_toTopOf="@+id/mapping_1_textview"
+        tools:text="HELLO" />
+
+    <TextView
+        android:id="@+id/mapping_3_textview"
+        style="?android:attr/spinnerItemStyle"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_30sdp"
+        android:ellipsize="marquee"
+        android:gravity="center"
+        android:singleLine="true"
+        android:textAlignment="inherit"
+
+        app:layout_constraintEnd_toStartOf="@id/mapping_4_textview"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/mapping_1_textview"
+
+        tools:text="HELLO" />
+
+    <TextView
+        android:id="@+id/editMapping_plus_3"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:text="+"
+
+
+        app:layout_constraintBottom_toBottomOf="@+id/mapping_3_textview"
+        app:layout_constraintEnd_toEndOf="@+id/mapping_3_textview"
+        app:layout_constraintStart_toEndOf="@id/mapping_3_textview"
+        app:layout_constraintTop_toBottomOf="@+id/mapping_1_textview" />
+
+    <TextView
+        android:id="@+id/mapping_4_textview"
+        style="?android:attr/spinnerItemStyle"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_30sdp"
+        android:ellipsize="marquee"
+        android:gravity="center"
+        android:singleLine="true"
+        android:textAlignment="inherit"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/mapping_3_textview"
+        app:layout_constraintTop_toTopOf="@id/mapping_3_textview"
+
+        tools:text="HELLO" />
+
+
+    <!-- ORIENTATION SECTION -->
+    <TextView
+        android:id="@+id/editOrientation_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/_2sdp"
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/customctrl_orientation"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/mapping_3_textview" />
+
+    <Spinner
+        android:id="@+id/editOrientation_spinner"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_30sdp"
+        android:background="@android:color/transparent"
+
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editOrientation_textView" />
+
+    <!-- TOGGLE SECTION -->
+    <Switch
+        android:id="@+id/checkboxToggle"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/_36sdp"
+        android:text="@string/customctrl_toggle"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/editOrientation_spinner"
+        tools:ignore="UseSwitchCompatOrMaterialXml" />
+
+    <!-- MOUSE PASS THROUGH SECTION -->
+    <Switch
+        android:id="@+id/checkboxPassThrough"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/_36sdp"
+        android:text="@string/customctrl_passthru"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/checkboxToggle"
+        tools:ignore="UseSwitchCompatOrMaterialXml" />
+
+    <!-- SWIPEABLE BUTTON SECTION -->
+    <Switch
+        android:id="@+id/checkboxSwipeable"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/_36sdp"
+        android:text="@string/customctrl_swipeable"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/checkboxPassThrough"
+        tools:ignore="UseSwitchCompatOrMaterialXml" />
+
+    <!-- FORWARD LOCK SECTION -->
+    <Switch
+        android:id="@+id/checkboxForwardLock"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/_36sdp"
+        android:text="@string/customctrl_forward_lock"
 
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/edit_layout"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/checkboxSwipeable"
+        tools:ignore="UseSwitchCompatOrMaterialXml" />
+
+    <Switch
+        android:id="@+id/checkboxAbsoluteFingerTracking"
         android:layout_width="match_parent"
+        android:layout_height="@dimen/_36sdp"
+        android:text="@string/customctrl_absolute_tracking"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/checkboxForwardLock"
+        tools:ignore="UseSwitchCompatOrMaterialXml"/>
+
+    <!-- BACKGROUND COLOR SECTION -->
+    <fr.spse.extended_view.ExtendedTextView
+        android:id="@+id/editBackgroundColor_textView"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_35sdp"
+        android:background="?attr/selectableItemBackground"
+        android:drawableEnd="@drawable/spinner_arrow_right"
+        android:gravity="center_vertical"
+        android:paddingEnd="5dp"
+        android:text="@string/customctrl_background_color"
+        app:drawableEndIntegerScaling="true"
+        app:drawableEndSize="20dp"
+
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/checkboxAbsoluteFingerTracking" />
+
+
+    <!-- STROKE WIDTH -->
+    <TextView
+        android:id="@+id/editStrokeWidth_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/_5sdp"
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/customctrl_stroke_width"
+
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/editStrokeColor_textView" />
+
+    <SeekBar
+        android:id="@+id/editStrokeWidth_seekbar"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_36sdp"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.538"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editStrokeWidth_textView" />
+
+    <TextView
+        android:id="@+id/editStrokeWidth_textView_percent"
+        android:layout_width="50dp"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:text="100%"
+
+        app:layout_constraintBottom_toBottomOf="@id/editStrokeWidth_textView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/editStrokeWidth_textView" />
+
+    <!-- STROKE COLOR VERSION -->
+    <fr.spse.extended_view.ExtendedTextView
+        android:id="@+id/editStrokeColor_textView"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_35sdp"
+        android:background="?attr/selectableItemBackground"
+        android:drawableEnd="@drawable/spinner_arrow_right"
+        android:gravity="center_vertical"
+        android:paddingEnd="5dp"
+        android:text="@string/customctrl_stroke_color"
+        app:drawableEndIntegerScaling="true"
+        app:drawableEndSize="20dp"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editBackgroundColor_textView" />
+
+
+    <!-- CORNER RADIUS SECTION -->
+    <TextView
+        android:id="@+id/editCornerRadius_textView"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:paddingHorizontal="@dimen/padding_moderate"
-        android:paddingVertical="@dimen/padding_moderate"
-        app:layout_optimizationLevel="standard|dimensions|chains">
-
-
-        <!-- EDIT NAME SECTION -->
-        <TextView
-            android:id="@+id/editName_textView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:paddingEnd="5dp"
-            android:text="@string/global_name"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
-        <EditText
-            android:id="@+id/editName_editText"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:hint="Button Name"
-            android:imeOptions="flagNoExtractUi"
-            android:maxLines="1"
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/editName_textView" />
-
-        <!--  SIZE SECTION -->
-        <TextView
-            android:id="@+id/editSize_textView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:paddingEnd="5dp"
-            android:text="@string/customctrl_size"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/editName_editText"
-            tools:visibility="visible" />
-
-        <EditText
-            android:id="@+id/editSize_editTextX"
-            android:layout_width="@dimen/_134sdp"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:imeOptions="flagNoExtractUi"
-            android:inputType="numberDecimal"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/editSize_textView" />
-
-        <EditText
-            android:id="@+id/editSize_editTextY"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:gravity="center"
-            android:imeOptions="flagNoExtractUi"
-            android:inputType="numberDecimal"
-
-            app:layout_constraintBottom_toBottomOf="@+id/editSize_editTextX"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toEndOf="@id/editSize_editTextX"
-            app:layout_constraintTop_toTopOf="@+id/editSize_editTextX" />
-
-        <TextView
-            android:id="@+id/editSize_x_textView"
-            android:layout_width="wrap_content"
-            android:layout_height="0dp"
-            android:gravity="center"
-            android:text="x"
-
-            app:layout_constraintBottom_toBottomOf="@+id/editSize_editTextX"
-            app:layout_constraintEnd_toStartOf="@id/editSize_editTextY"
-            app:layout_constraintStart_toEndOf="@id/editSize_editTextX"
-            app:layout_constraintTop_toTopOf="@+id/editSize_editTextX" />
-
-        <!-- MAPPING SECTION -->
-        <TextView
-            android:id="@+id/editMapping_textView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-
-            android:paddingEnd="5dp"
-            android:text="@string/customctrl_mapping"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/editSize_editTextX" />
-
-        <!--
-            Spinners are hidden to handle to workaround the horizontal offset.
-            The horizontal offset was needed to display the spinner list at the same place.
-            Sadly it is broken since Android Jelly Bean and never scheduled to be fixed
-        -->
-        <Spinner
-            android:id="@+id/editMapping_spinner_1"
-            android:layout_width="0dp"
-            android:layout_height="30dp"
-            android:dropDownWidth="match_parent"
-            android:dropDownVerticalOffset="30dp"
-            android:gravity="center"
-            android:visibility="invisible"
-            android:background="@android:color/transparent"
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/editMapping_textView" />
-
-
-        <Spinner
-            android:id="@+id/editMapping_spinner_2"
-            android:layout_width="0dp"
-            android:layout_height="30dp"
-            android:dropDownWidth="match_parent"
-            android:dropDownHorizontalOffset="30dp"
-            android:dropDownVerticalOffset="30dp"
-            android:gravity="center"
-            android:visibility="invisible"
-            android:background="@android:color/transparent"
-
-            app:layout_constraintTop_toBottomOf="@+id/editMapping_textView"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent" />
-
-        <Spinner
-            android:id="@+id/editMapping_spinner_3"
-            android:layout_width="0dp"
-            android:layout_height="30dp"
-            android:dropDownWidth="match_parent"
-            android:dropDownVerticalOffset="30dp"
-            android:gravity="center"
-            android:visibility="invisible"
-            android:background="@android:color/transparent"
-
-            app:layout_constraintTop_toTopOf="@+id/mapping_3_textview"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent" />
-
-
-        <Spinner
-            android:id="@+id/editMapping_spinner_4"
-            android:layout_width="0dp"
-            android:layout_height="30dp"
-            android:dropDownWidth="match_parent"
-            android:dropDownVerticalOffset="30dp"
-            android:visibility="invisible"
-            android:background="@android:color/transparent"
-
-            app:layout_constraintTop_toTopOf="@+id/mapping_3_textview"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent" />
-
-        <!-- Clickable textview linked to the spinners
-            Spinners are hidden at the same place because the dropdown offset doesn't work
-            Do those textview acts as the spinner idle state
-        -->
-
-        <TextView
-            android:id="@+id/mapping_1_textview"
-            style="?android:attr/spinnerItemStyle"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_30sdp"
-            android:ellipsize="marquee"
-            android:gravity="center"
-            android:singleLine="true"
-            android:textAlignment="inherit"
-
-            app:layout_constraintEnd_toStartOf="@id/mapping_2_textview"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/editMapping_textView"
-
-            tools:text="HELLO" />
-
-        <TextView
-            android:id="@+id/editMapping_plus_1"
-            android:layout_width="wrap_content"
-            android:layout_height="0dp"
-            android:gravity="center"
-            android:text="+"
-
-            app:layout_constraintBottom_toBottomOf="@+id/mapping_1_textview"
-            app:layout_constraintEnd_toEndOf="@+id/mapping_1_textview"
-            app:layout_constraintStart_toEndOf="@id/mapping_1_textview"
-            app:layout_constraintTop_toTopOf="@+id/mapping_1_textview" />
-
-        <TextView
-            android:id="@+id/mapping_2_textview"
-            style="?android:attr/spinnerItemStyle"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_30sdp"
-            android:ellipsize="marquee"
-            android:gravity="center"
-            android:singleLine="true"
-            android:textAlignment="inherit"
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toEndOf="@id/mapping_1_textview"
-
-            app:layout_constraintTop_toTopOf="@+id/mapping_1_textview"
-            tools:text="HELLO" />
-
-        <TextView
-            android:id="@+id/mapping_3_textview"
-            style="?android:attr/spinnerItemStyle"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_30sdp"
-            android:ellipsize="marquee"
-            android:gravity="center"
-            android:singleLine="true"
-            android:textAlignment="inherit"
-
-            app:layout_constraintEnd_toStartOf="@id/mapping_4_textview"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/mapping_1_textview"
-
-            tools:text="HELLO" />
-
-        <TextView
-            android:id="@+id/editMapping_plus_3"
-            android:layout_width="wrap_content"
-            android:layout_height="0dp"
-            android:gravity="center"
-            android:text="+"
-
-
-            app:layout_constraintBottom_toBottomOf="@+id/mapping_3_textview"
-            app:layout_constraintEnd_toEndOf="@+id/mapping_3_textview"
-            app:layout_constraintStart_toEndOf="@id/mapping_3_textview"
-            app:layout_constraintTop_toBottomOf="@+id/mapping_1_textview" />
-
-        <TextView
-            android:id="@+id/mapping_4_textview"
-            style="?android:attr/spinnerItemStyle"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_30sdp"
-            android:ellipsize="marquee"
-            android:gravity="center"
-            android:singleLine="true"
-            android:textAlignment="inherit"
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toEndOf="@id/mapping_3_textview"
-            app:layout_constraintTop_toTopOf="@id/mapping_3_textview"
-
-            tools:text="HELLO" />
-
-
-        <!-- ORIENTATION SECTION -->
-        <TextView
-            android:id="@+id/editOrientation_textView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/_2sdp"
-            android:gravity="center"
-            android:paddingEnd="5dp"
-            android:text="@string/customctrl_orientation"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/mapping_3_textview" />
-
-        <Spinner
-            android:id="@+id/editOrientation_spinner"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_30sdp"
-            android:background="@android:color/transparent"
-
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/editOrientation_textView" />
-
-        <!-- TOGGLE SECTION -->
-        <Switch
-            android:id="@+id/checkboxToggle"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/_36sdp"
-            android:text="@string/customctrl_toggle"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/editOrientation_spinner"
-            tools:ignore="UseSwitchCompatOrMaterialXml" />
-
-        <!-- MOUSE PASS THROUGH SECTION -->
-        <Switch
-            android:id="@+id/checkboxPassThrough"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/_36sdp"
-            android:text="@string/customctrl_passthru"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/checkboxToggle"
-            tools:ignore="UseSwitchCompatOrMaterialXml" />
-
-        <!-- SWIPEABLE BUTTON SECTION -->
-        <Switch
-            android:id="@+id/checkboxSwipeable"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/_36sdp"
-            android:text="@string/customctrl_swipeable"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/checkboxPassThrough"
-            tools:ignore="UseSwitchCompatOrMaterialXml" />
-
-        <!-- FORWARD LOCK SECTION -->
-        <Switch
-            android:id="@+id/checkboxForwardLock"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/_36sdp"
-            android:text="@string/customctrl_forward_lock"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/checkboxSwipeable"
-            tools:ignore="UseSwitchCompatOrMaterialXml" />
-
-        <Switch
-            android:id="@+id/checkboxAbsoluteFingerTracking"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/_36sdp"
-            android:text="@string/customctrl_absolute_tracking"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/checkboxForwardLock"
-            tools:ignore="UseSwitchCompatOrMaterialXml"/>
-
-        <!-- BACKGROUND COLOR SECTION -->
-        <fr.spse.extended_view.ExtendedTextView
-            android:id="@+id/editBackgroundColor_textView"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_35sdp"
-            android:background="?attr/selectableItemBackground"
-            android:drawableEnd="@drawable/spinner_arrow_right"
-            android:gravity="center_vertical"
-            android:paddingEnd="5dp"
-            android:text="@string/customctrl_background_color"
-            app:drawableEndIntegerScaling="true"
-            app:drawableEndSize="20dp"
-
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/checkboxAbsoluteFingerTracking" />
-
-
-        <!-- STROKE WIDTH -->
-        <TextView
-            android:id="@+id/editStrokeWidth_textView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/_5sdp"
-            android:gravity="center"
-            android:paddingEnd="5dp"
-            android:text="@string/customctrl_stroke_width"
-
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/editStrokeColor_textView" />
-
-        <SeekBar
-            android:id="@+id/editStrokeWidth_seekbar"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_36sdp"
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="0.538"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/editStrokeWidth_textView" />
-
-        <TextView
-            android:id="@+id/editStrokeWidth_textView_percent"
-            android:layout_width="50dp"
-            android:layout_height="0dp"
-            android:gravity="center"
-            android:text="100%"
-
-            app:layout_constraintBottom_toBottomOf="@id/editStrokeWidth_textView"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/editStrokeWidth_textView" />
-
-        <!-- STROKE COLOR VERSION -->
-        <fr.spse.extended_view.ExtendedTextView
-            android:id="@+id/editStrokeColor_textView"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_35sdp"
-            android:background="?attr/selectableItemBackground"
-            android:drawableEnd="@drawable/spinner_arrow_right"
-            android:gravity="center_vertical"
-            android:paddingEnd="5dp"
-            android:text="@string/customctrl_stroke_color"
-            app:drawableEndIntegerScaling="true"
-            app:drawableEndSize="20dp"
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/editBackgroundColor_textView" />
-
-
-        <!-- CORNER RADIUS SECTION -->
-        <TextView
-            android:id="@+id/editCornerRadius_textView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:paddingEnd="5dp"
-            android:text="@string/customctrl_corner_radius"
-
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/editStrokeWidth_seekbar" />
-
-        <SeekBar
-            android:id="@+id/editCornerRadius_seekbar"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_36sdp"
-
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/editCornerRadius_textView" />
-
-        <TextView
-            android:id="@+id/editCornerRadius_textView_percent"
-            android:layout_width="50dp"
-            android:layout_height="0dp"
-            android:gravity="center"
-            android:text="100%"
-
-            app:layout_constraintBottom_toBottomOf="@id/editCornerRadius_textView"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/editCornerRadius_textView" />
-
-        <!-- BUTTON OPACITY SECTION -->
-        <TextView
-            android:id="@+id/editButtonOpacity_textView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:paddingEnd="5dp"
-            android:text="@string/customctrl_button_opacity"
-
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/editCornerRadius_seekbar" />
-
-        <SeekBar
-            android:id="@+id/editButtonOpacity_seekbar"
-            android:layout_width="0dp"
-            android:layout_height="@dimen/_36sdp"
-
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/editButtonOpacity_textView" />
-
-        <TextView
-            android:id="@+id/editButtonOpacity_textView_percent"
-            android:layout_width="50dp"
-            android:layout_height="0dp"
-            android:gravity="center"
-            android:text="100%"
-
-            app:layout_constraintBottom_toBottomOf="@id/editButtonOpacity_textView"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/editButtonOpacity_textView" />
-
-        <!-- Button visibility -->
-        <TextView
-            android:id="@+id/visibility_textview"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/customctrl_visibility_title"
-            app:layout_constraintTop_toBottomOf="@+id/editButtonOpacity_seekbar"
-            tools:layout_editor_absoluteX="6dp" />
-
-        <CheckBox
-            android:id="@+id/visibility_game_checkbox"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/customctrl_visibility_ingame"
-            app:layout_constraintTop_toBottomOf="@+id/visibility_textview" />
-
-        <CheckBox
-            android:id="@+id/visibility_menu_checkbox"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/customctrl_visibility_in_menus"
-            app:layout_constraintTop_toBottomOf="@+id/visibility_game_checkbox" />
-
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-</com.kdt.DefocusableScrollView>
\ No newline at end of file
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/customctrl_corner_radius"
+
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/editStrokeWidth_seekbar" />
+
+    <SeekBar
+        android:id="@+id/editCornerRadius_seekbar"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_36sdp"
+
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editCornerRadius_textView" />
+
+    <TextView
+        android:id="@+id/editCornerRadius_textView_percent"
+        android:layout_width="50dp"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:text="100%"
+
+        app:layout_constraintBottom_toBottomOf="@id/editCornerRadius_textView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/editCornerRadius_textView" />
+
+    <!-- BUTTON OPACITY SECTION -->
+    <TextView
+        android:id="@+id/editButtonOpacity_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/customctrl_button_opacity"
+
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editCornerRadius_seekbar" />
+
+    <SeekBar
+        android:id="@+id/editButtonOpacity_seekbar"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_36sdp"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/editButtonOpacity_textView" />
+
+    <TextView
+        android:id="@+id/editButtonOpacity_textView_percent"
+        android:layout_width="50dp"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:text="100%"
+
+        app:layout_constraintBottom_toBottomOf="@id/editButtonOpacity_textView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/editButtonOpacity_textView" />
+
+    <!-- Button visibility -->
+    <TextView
+        android:id="@+id/visibility_textview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/customctrl_visibility_title"
+        app:layout_constraintTop_toBottomOf="@+id/editButtonOpacity_seekbar"
+        tools:layout_editor_absoluteX="6dp" />
+
+    <CheckBox
+        android:id="@+id/visibility_game_checkbox"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/customctrl_visibility_ingame"
+        app:layout_constraintTop_toBottomOf="@+id/visibility_textview" />
+
+    <CheckBox
+        android:id="@+id/visibility_menu_checkbox"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/customctrl_visibility_in_menus"
+        app:layout_constraintTop_toBottomOf="@+id/visibility_game_checkbox" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+
diff --git a/app_pojavlauncher/src/main/res/layout/dialog_quick_setting.xml b/app_pojavlauncher/src/main/res/layout/dialog_quick_setting.xml
new file mode 100644
index 0000000000..435781a042
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/layout/dialog_quick_setting.xml
@@ -0,0 +1,191 @@
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/edit_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingHorizontal="@dimen/padding_moderate"
+    android:paddingVertical="@dimen/padding_moderate"
+    app:layout_optimizationLevel="standard|dimensions|chains">
+
+    <!-- Resolution settings -->
+    <TextView
+        android:id="@+id/editResolution_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/_5sdp"
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/mcl_setting_title_resolution_scaler"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.kdt.CustomSeekbar
+        android:id="@+id/editResolution_seekbar"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_36sdp"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.538"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editResolution_textView" />
+
+    <TextView
+        android:id="@+id/editResolution_textView_percent"
+        android:layout_width="50dp"
+        android:layout_height="0dp"
+        android:gravity="center"
+        tools:text="50%"
+
+        app:layout_constraintBottom_toBottomOf="@id/editResolution_textView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/editResolution_textView" />
+
+    <!-- Gyroscope settings -->
+    <Switch
+        android:id="@+id/checkboxGyro"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/_36sdp"
+        android:text="@string/preference_enable_gyro_title"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/editResolution_seekbar"
+        tools:ignore="UseSwitchCompatOrMaterialXml" />
+
+    <Switch
+        android:id="@+id/checkboxGyroX"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/_36sdp"
+        android:text="@string/preference_gyro_invert_x_axis"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/checkboxGyro"
+        tools:ignore="UseSwitchCompatOrMaterialXml" />
+
+    <Switch
+        android:id="@+id/checkboxGyroY"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/_36sdp"
+        android:text="@string/preference_gyro_invert_y_axis"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/checkboxGyroX"
+        tools:ignore="UseSwitchCompatOrMaterialXml" />
+
+    <!-- Gyroscope sensitivity -->
+    <TextView
+        android:id="@+id/editGyro_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/_5sdp"
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/preference_gyro_sensitivity_title"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/checkboxGyroY" />
+
+    <com.kdt.CustomSeekbar
+        android:id="@+id/editGyro_seekbar"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_36sdp"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.538"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editGyro_textView" />
+
+    <TextView
+        android:id="@+id/editGyro_textView_percent"
+        android:layout_width="50dp"
+        android:layout_height="0dp"
+        android:gravity="center"
+        tools:text="100%"
+
+        app:layout_constraintBottom_toBottomOf="@id/editGyro_textView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/editGyro_textView" />
+
+
+    <!-- Mouse settings -->
+    <TextView
+        android:id="@+id/editMouseSpeed_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/_5sdp"
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/mcl_setting_title_mousespeed"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/editGyro_seekbar" />
+
+    <com.kdt.CustomSeekbar
+        android:id="@+id/editMouseSpeed_seekbar"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_36sdp"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.538"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editMouseSpeed_textView" />
+
+    <TextView
+        android:id="@+id/editMouseSpeed_textView_percent"
+        android:layout_width="50dp"
+        android:layout_height="0dp"
+        android:gravity="center"
+        tools:text="100%"
+
+        app:layout_constraintBottom_toBottomOf="@id/editMouseSpeed_textView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/editMouseSpeed_textView" />
+
+    <!-- Gesture toggle -->
+    <Switch
+        android:id="@+id/checkboxGesture"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/_36sdp"
+        android:text="@string/mcl_disable_gestures"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/editMouseSpeed_seekbar"
+        tools:ignore="UseSwitchCompatOrMaterialXml" />
+
+    <!-- gesture settings -->
+    <TextView
+        android:id="@+id/editGestureDelay_textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/_5sdp"
+        android:gravity="center"
+        android:paddingEnd="5dp"
+        android:text="@string/mcl_setting_title_longpresstrigger"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/checkboxGesture" />
+
+    <com.kdt.CustomSeekbar
+        android:id="@+id/editGestureDelay_seekbar"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/_36sdp"
+
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.538"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/editGestureDelay_textView" />
+
+    <TextView
+        android:id="@+id/editGestureDelay_textView_percent"
+        android:layout_width="50dp"
+        android:layout_height="0dp"
+        android:gravity="center"
+        tools:text="270ms"
+
+        app:layout_constraintBottom_toBottomOf="@id/editGestureDelay_textView"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/editGestureDelay_textView" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app_pojavlauncher/src/main/res/layout/dialog_side_dialog.xml b/app_pojavlauncher/src/main/res/layout/dialog_side_dialog.xml
new file mode 100644
index 0000000000..053eb485e4
--- /dev/null
+++ b/app_pojavlauncher/src/main/res/layout/dialog_side_dialog.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="@dimen/_280sdp"
+    android:layout_marginVertical="@dimen/padding_heavy"
+    android:orientation="vertical"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:background="@drawable/background_control_editor">
+    <TextView
+        android:id="@+id/side_dialog_title_textview"
+        style="@style/TextAppearance.AppCompat.Large"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/padding_moderate"
+        android:visibility="gone"
+        app:layout_constraintStart_toStartOf="parent"
+
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="Quick settings"
+        tools:visibility="visible" />
+    <View
+        android:id="@+id/side_dialog_title_divider"
+        style="@style/ThickDivider"
+        android:layout_width="match_parent"
+        android:paddingHorizontal="@dimen/padding_moderate"
+        android:visibility="gone"
+
+        tools:visibility="visible"
+        />
+    <com.kdt.DefocusableScrollView
+        android:id="@+id/side_dialog_scrollview"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        tools:background="@color/background_status_bar">
+        <!-- The content is inflated here -->
+    </com.kdt.DefocusableScrollView>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/side_dialog_start_button"
+            style="@style/Widget.AppCompat.Button.Borderless"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="true"
+            android:visibility="gone"
+
+            tools:visibility="visible"
+            tools:text="Cancel"
+            />
+
+        <Button
+            android:id="@+id/side_dialog_end_button"
+            style="@style/Widget.AppCompat.Button.Borderless"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:visibility="gone"
+
+            tools:text="Confirm"
+            tools:visibility="visible"
+            tools:ignore="RelativeOverlap" />
+        <!-- Appears to be no way to properly fix this, and I'm not super fond of child ConstraintLayouts -->
+    </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/app_pojavlauncher/src/main/res/menu/menu_runopt.xml b/app_pojavlauncher/src/main/res/menu/menu_runopt.xml
deleted file mode 100644
index 7530b7ff9f..0000000000
--- a/app_pojavlauncher/src/main/res/menu/menu_runopt.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item
-        android:id="@+id/nav_forceclose"
-        android:title="@string/control_forceclose" />
-    <item
-        android:id="@+id/nav_viewlog"
-        android:title="@string/control_viewout" />
-    <item
-        android:id="@+id/nav_debug"
-        android:title="@string/control_adebug" />
-    <item
-        android:id="@+id/nav_customkey"
-        android:title="@string/control_customkey" />
-    <item
-        android:id="@+id/nav_mousespd"
-        android:title="@string/mcl_setting_title_mousespeed" />
-    <item
-        android:id="@+id/nav_customctrl"
-        android:title="@string/mcl_option_customcontrol" />
-</menu>
diff --git a/app_pojavlauncher/src/main/res/values/headings_array.xml b/app_pojavlauncher/src/main/res/values/headings_array.xml
index b5a3256bb0..b8ad18d4bd 100644
--- a/app_pojavlauncher/src/main/res/values/headings_array.xml
+++ b/app_pojavlauncher/src/main/res/values/headings_array.xml
@@ -27,11 +27,9 @@
     </string-array>
 
     <string-array name="menu_ingame">
-        <item>@string/control_forceclose</item>
         <item>@string/control_viewout</item>
         <item>@string/control_customkey</item>
-        <item>@string/mcl_setting_title_mousespeed</item>
-        <item>@string/preference_gyro_sensitivity_title</item>
+        <item>@string/quick_setting_title</item>
         <item>@string/mcl_option_customcontrol</item>
     </string-array>
     
diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml
index f5a3857616..cfc5affce1 100644
--- a/app_pojavlauncher/src/main/res/values/strings.xml
+++ b/app_pojavlauncher/src/main/res/values/strings.xml
@@ -274,6 +274,7 @@
     <string name="preference_shader_dump_title">Enable shader dumping</string>
     <string name="preference_shader_dump_description">Log converted shaders into the log file.</string>
     <string name="percent_format">%d%%</string>
+    <string name="millisecond_format">%d ms</string>
     <string name="customctrl_editor_exit_title">Leaving?</string>
     <string name="customctrl_editor_exit_msg">Are you sure you want to exit?</string>
     <string name="customctrl_editor_exit">Leave editor</string>
@@ -419,4 +420,5 @@
     <string name="discord_invite" translatable="false">https://discord.com/invite/aenk3EUvER</string>
     <string name="local_login_bad_username_title">Unsuitable username</string>
     <string name="local_login_bad_username_text">The username must be between 3–16 characters long, and must only contain latin letters, arabic numerals and underscores.</string>
+    <string name="quick_setting_title">Quick settings</string>
 </resources>
diff --git a/app_pojavlauncher/src/main/res/values/styles.xml b/app_pojavlauncher/src/main/res/values/styles.xml
index a9b2199afc..4da162a723 100644
--- a/app_pojavlauncher/src/main/res/values/styles.xml
+++ b/app_pojavlauncher/src/main/res/values/styles.xml
@@ -33,4 +33,10 @@
         <item name="android:background">?android:attr/listDivider</item>
     </style>
 
+    <item name="dimension_tracker" type="id" />
+    <style name="DimensionTracker">
+        <item name="android:id">@id/dimension_tracker</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+    </style>
 </resources>
diff --git a/jre_lwjgl3glfw/libs/lwjgl-lwjglx.jar b/jre_lwjgl3glfw/libs/lwjgl-lwjglx.jar
index 648316323a..45530a23f4 100644
Binary files a/jre_lwjgl3glfw/libs/lwjgl-lwjglx.jar and b/jre_lwjgl3glfw/libs/lwjgl-lwjglx.jar differ
diff --git a/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/GLFW.java b/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/GLFW.java
index 3a1b9c74fb..53ea33596a 100644
--- a/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/GLFW.java
+++ b/jre_lwjgl3glfw/src/main/java/org/lwjgl/glfw/GLFW.java
@@ -478,7 +478,6 @@ public class GLFW
     /* volatile */ public static GLFWCursorPosCallback mGLFWCursorPosCallback;
     /* volatile */ public static GLFWDropCallback mGLFWDropCallback;
     /* volatile */ public static GLFWErrorCallback mGLFWErrorCallback;
-    /* volatile */ public static GLFWFramebufferSizeCallback mGLFWFramebufferSizeCallback;
     /* volatile */ public static GLFWJoystickCallback mGLFWJoystickCallback;
     /* volatile */ public static GLFWKeyCallback mGLFWKeyCallback;
     /* volatile */ public static GLFWMonitorCallback mGLFWMonitorCallback;
@@ -491,7 +490,11 @@ public class GLFW
     /* volatile */ public static GLFWWindowMaximizeCallback mGLFWWindowMaximizeCallback;
     /* volatile */ public static GLFWWindowPosCallback mGLFWWindowPosCallback;
     /* volatile */ public static GLFWWindowRefreshCallback mGLFWWindowRefreshCallback;
-    /* volatile */ public static GLFWWindowSizeCallback mGLFWWindowSizeCallback;
+
+    // Store callback method references directly to avoid a roundtrip through
+    // JNI when calling the default LWJGL callbacks.
+    @Nullable public static GLFWFramebufferSizeCallbackI mGLFWFramebufferSizeCallbackI;
+    @Nullable public static GLFWWindowSizeCallbackI mGLFWWindowSizeCallbackI;
 
     volatile public static int mGLFWWindowWidth, mGLFWWindowHeight;
 
@@ -514,22 +517,6 @@ public class GLFW
     public static long mainContext = 0;
 
     static {
-        String windowWidth = System.getProperty(PROP_WINDOW_WIDTH);
-        String windowHeight = System.getProperty(PROP_WINDOW_HEIGHT);
-        if (windowWidth == null || windowHeight == null) {
-            System.err.println("Warning: Property " + PROP_WINDOW_WIDTH + " or " + PROP_WINDOW_HEIGHT + " not set, defaulting to 1280 and 720");
-
-            mGLFWWindowWidth = 1280;
-            mGLFWWindowHeight = 720;
-        } else {
-            mGLFWWindowWidth = Integer.parseInt(windowWidth);
-            mGLFWWindowHeight = Integer.parseInt(windowHeight);
-        }
-
-        // Minecraft triggers a glfwPollEvents() on splash screen, so update window size there.
-        // CallbackBridge.receiveCallback(CallbackBridge.EVENT_TYPE_FRAMEBUFFER_SIZE, mGLFWWindowWidth, mGLFWWindowHeight, 0, 0);
-        // CallbackBridge.receiveCallback(CallbackBridge.EVENT_TYPE_WINDOW_SIZE, mGLFWWindowWidth, mGLFWWindowHeight, 0, 0);
-
         try {
             System.loadLibrary("pojavexec");
         } catch (UnsatisfiedLinkError e) {
@@ -583,11 +570,9 @@ public void callback(long args) {
     private static native long nglfwSetCharModsCallback(long window, long ptr);
     private static native long nglfwSetCursorEnterCallback(long window, long ptr);
     private static native long nglfwSetCursorPosCallback(long window, long ptr);
-    private static native long nglfwSetFramebufferSizeCallback(long window, long ptr);
     private static native long nglfwSetKeyCallback(long window, long ptr);
     private static native long nglfwSetMouseButtonCallback(long window, long ptr);
     private static native long nglfwSetScrollCallback(long window, long ptr);
-    private static native long nglfwSetWindowSizeCallback(long window, long ptr);
     // private static native void nglfwSetInputReady();
     private static native void nglfwSetShowingWindow(long window);
 
@@ -634,6 +619,7 @@ public static SharedLibrary getLibrary() {
         return GLFW;
     }
 
+    @SuppressWarnings("unused") // Used by pojavexec
     public static void internalChangeMonitorSize(int width, int height) {
         mGLFWWindowWidth = width;
         mGLFWWindowHeight = height;
@@ -700,11 +686,12 @@ public static GLFWErrorCallback glfwSetErrorCallback(@Nullable @NativeType("GLFW
     }
 
     public static GLFWFramebufferSizeCallback glfwSetFramebufferSizeCallback(@NativeType("GLFWwindow *") long window, @Nullable @NativeType("GLFWframebuffersizefun") GLFWFramebufferSizeCallbackI cbfun) {
-        GLFWFramebufferSizeCallback lastCallback = mGLFWFramebufferSizeCallback;
-        if (cbfun == null) mGLFWFramebufferSizeCallback = null;
-        else mGLFWFramebufferSizeCallback = GLFWFramebufferSizeCallback.createSafe(nglfwSetFramebufferSizeCallback(window, memAddressSafe(cbfun)));
-
-        return lastCallback;
+        GLFWFramebufferSizeCallback previousCallback = null;
+        if(mGLFWFramebufferSizeCallbackI != null) {
+            previousCallback = GLFWFramebufferSizeCallback.create(mGLFWFramebufferSizeCallbackI);
+        }
+        mGLFWFramebufferSizeCallbackI = cbfun;
+        return previousCallback;
     }
 
     public static GLFWJoystickCallback glfwSetJoystickCallback(/* @NativeType("GLFWwindow *") long window, */ @Nullable @NativeType("GLFWjoystickfun") GLFWJoystickCallbackI cbfun) {
@@ -803,11 +790,12 @@ public static GLFWWindowRefreshCallback glfwSetWindowRefreshCallback(@NativeType
     }
 
     public static GLFWWindowSizeCallback glfwSetWindowSizeCallback(@NativeType("GLFWwindow *") long window, @Nullable @NativeType("GLFWwindowsizefun") GLFWWindowSizeCallbackI cbfun) {
-        GLFWWindowSizeCallback lastCallback = mGLFWWindowSizeCallback;
-        if (cbfun == null) mGLFWWindowSizeCallback = null;
-        else mGLFWWindowSizeCallback = GLFWWindowSizeCallback.createSafe(nglfwSetWindowSizeCallback(window, memAddressSafe(cbfun)));
-
-        return lastCallback;
+        GLFWWindowSizeCallback previousCallback = null;
+        if(mGLFWWindowSizeCallbackI != null) {
+            previousCallback = GLFWWindowSizeCallback.create(mGLFWWindowSizeCallbackI);
+        }
+        mGLFWWindowSizeCallbackI = cbfun;
+        return previousCallback;
     }
 
     static boolean isGLFWReady;
@@ -1102,11 +1090,12 @@ public static void glfwPollEvents() {
         callV(Functions.StopPumping);
         mGLFWInputPumping = false;
     }
-
-    public static void internalWindowSizeChanged(long window, int w, int h) {
+    @SuppressWarnings("unused") // Used by pojavexec
+    public static void internalWindowSizeChanged(long window) {
         try {
-            internalChangeMonitorSize(w, h);
             glfwSetWindowSize(window, mGLFWWindowWidth, mGLFWWindowHeight);
+            if(mGLFWFramebufferSizeCallbackI != null) mGLFWFramebufferSizeCallbackI.invoke(window, mGLFWWindowWidth, mGLFWWindowHeight);
+            if(mGLFWWindowSizeCallbackI != null) mGLFWWindowSizeCallbackI.invoke(window, mGLFWWindowWidth, mGLFWWindowHeight);
         }catch (Exception e) {
             e.printStackTrace();
         }


Test patch: