Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat[launcher]: Add a quick settings menu #6303

Merged
merged 32 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
192cd74
Feat[launcher]: changes related to game obtaining and updating screen…
artdeell Nov 12, 2024
d448975
Add: Range + increment seekbar component
Mathias-Boulay Nov 18, 2024
f64964c
Add: simple seekbar listener
Mathias-Boulay Nov 18, 2024
d6a6c89
Cleanup: Remove unused resource
Mathias-Boulay Nov 18, 2024
7481278
Add: Side dialog system
Mathias-Boulay Nov 18, 2024
3777261
Refactor: move static functions to Tools
Mathias-Boulay Nov 18, 2024
d6534d5
Feat: In game quick settings
Mathias-Boulay Nov 18, 2024
be69154
Fix: resolution not being properly reflected
Mathias-Boulay Nov 18, 2024
85e98a9
Refactor(gyro): pre-compute the factor one quick setting change
Mathias-Boulay Nov 19, 2024
dc06417
Fix: propagate resolution scale change to all components
Mathias-Boulay Nov 19, 2024
2e9428d
Tweak: only instantiate the dialog when first needed
Mathias-Boulay Nov 19, 2024
3f5baec
Fix(hotbar): make the hotbar aware of the quick setting resolution
Mathias-Boulay Nov 19, 2024
924c33c
Fix[touchpad]: respond to dynamic scale changes
artdeell Nov 20, 2024
b7fa7eb
Fix[side_dialog]: make the dialog scale properly with wrap_content
artdeell Nov 21, 2024
ff9e588
Refactor(side dialog): Allow the dialog to remove its views from the …
Mathias-Boulay Nov 20, 2024
0e06203
Refactor(controls): retrofit edit controls to sideDialog
Mathias-Boulay Nov 21, 2024
a93d7b0
Fix(side dialog): height was incorrect
Mathias-Boulay Nov 21, 2024
371494c
Fix(quick settings): persisted settings after cancel
Mathias-Boulay Nov 21, 2024
9c12611
Refactor(quick settings): remove generic leading to unchecked typecast
Mathias-Boulay Nov 21, 2024
1aface5
Fix[lwjglx]: mouse jump on Y axis when changing resolution
artdeell Nov 21, 2024
6888de8
Refactor(quick settings): better lifecycle interface
Mathias-Boulay Nov 21, 2024
bb03cb8
Feat[color_selector]: rewrite to be a SideDialogView
artdeell Nov 22, 2024
14ba6e2
Fix[quick_setting_dialog]: fix seek text format warning, apply defaul…
artdeell Nov 22, 2024
b5de32d
Fix[input_bridge]: cast pointer to jlong instead of long
artdeell Nov 22, 2024
fe78b94
Fix[quick_settings]: crash when leaving main activity
artdeell Nov 22, 2024
f16c039
Fix[input_bridge]: resizing race condition
artdeell Nov 22, 2024
62de3d3
Fix[gyro_control]: implement sensor warmup period for devices with br…
artdeell Nov 22, 2024
383b3ed
Fix[gyro_control]: whoops, forgot to remove the log print
artdeell Nov 22, 2024
19afab2
Fix: hide gyro if not supported
Mathias-Boulay Nov 22, 2024
19dc51f
cleanup: remove unneeded menu entry
Mathias-Boulay Nov 22, 2024
03eaf12
Fix(settings): wrong order typecast
Mathias-Boulay Nov 25, 2024
eb6b23c
Merge branch 'v3_openjdk' into feat/quick_settings
artdeell Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1720909595576
1732218529630
138 changes: 138 additions & 0 deletions app_pojavlauncher/src/main/java/com/kdt/CustomSeekbar.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
246 changes: 246 additions & 0 deletions app_pojavlauncher/src/main/java/com/kdt/SideDialogView.java
Original file line number Diff line number Diff line change
@@ -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() {}


}
Loading
Loading