Feat|Refactor: New control editing workflow

The new worflow is mostly focused on the ability to change in real time visual features of a button.
It also brings a new *export* button to easily export controls you made.
This commit is contained in:
Boulay Mathias 2022-11-05 16:18:44 +01:00
parent 53cd15ac7d
commit d1c88a19e0
41 changed files with 2082 additions and 2310 deletions

View file

@ -105,6 +105,22 @@
</intent-filter>
</provider>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="share_file"
android:exported="false"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider"
/>
</provider>
</application>
</manifest>

View file

@ -1,11 +1,15 @@
package net.kdt.pojavlaunch;
import static androidx.core.content.FileProvider.getUriForFile;
import android.app.Activity;
import android.content.*;
import android.net.Uri;
import android.os.*;
import androidx.appcompat.app.*;
import android.view.View;
import android.widget.*;
import androidx.drawerlayout.widget.DrawerLayout;
@ -30,34 +34,39 @@ public class CustomControlsActivity extends BaseActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getExtras() != null && getIntent().getExtras().getBoolean("fromMainActivity", false)) {
// TODO translucent!
// setTheme(androidx.appcompat.R.style.Theme_AppCompat_Translucent);
}
setContentView(R.layout.activity_custom_controls);
mControlLayout = (ControlLayout) findViewById(R.id.customctrl_controllayout);
mDrawerLayout = (DrawerLayout) findViewById(R.id.customctrl_drawerlayout);
mDrawerNavigationView = (ListView) findViewById(R.id.customctrl_navigation_view);
mControlLayout = findViewById(R.id.customctrl_controllayout);
mDrawerLayout = findViewById(R.id.customctrl_drawerlayout);
mDrawerNavigationView = findViewById(R.id.customctrl_navigation_view);
View mPullDrawerButton = findViewById(R.id.drawer_button);
mDrawerNavigationView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,getResources().getStringArray(R.array.menu_customcontrol)));
mPullDrawerButton.setOnClickListener(v -> mDrawerLayout.openDrawer(mDrawerNavigationView));
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
mDrawerNavigationView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,getResources().getStringArray(R.array.menu_customcontrol_customactivity)));
mDrawerNavigationView.setOnItemClickListener((parent, view, position, id) -> {
switch(position) {
case 0:
mControlLayout.addControlButton(new ControlData("New"));
break;
case 1:
mControlLayout.addDrawer(new ControlDrawerData());
break;
case 2:
load(mControlLayout);
break;
case 3:
save(false, mControlLayout);
break;
case 4:
dialogSelectDefaultCtrl(mControlLayout);
case 0: mControlLayout.addControlButton(new ControlData("New")); break;
case 1: mControlLayout.addDrawer(new ControlDrawerData()); break;
//case 2: mControlLayout.addJoystickButton(new ControlData()); break;
case 2: load(mControlLayout); break;
case 3: save(false, mControlLayout); break;
case 4: dialogSelectDefaultCtrl(mControlLayout); break;
case 5: // Saving the currently shown control
mControlLayout.save(Tools.DIR_DATA + "/files/" + sSelectedName + ".json");
Uri contentUri = getUriForFile(getBaseContext(), "share_file", new File(Tools.DIR_DATA, "/files/" + sSelectedName + ".json"));
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.setType("application/json");
startActivity(shareIntent);
Intent sendIntent = Intent.createChooser(shareIntent, sSelectedName);
startActivity(sendIntent);
break;
}
mDrawerLayout.closeDrawers();

View file

@ -61,6 +61,7 @@ public class MainActivity extends BaseActivity {
private DrawerLayout drawerLayout;
private ListView navDrawer;
private View mDrawerPullButton;
private ArrayAdapter<String> gameActionArrayAdapter;
private AdapterView.OnItemClickListener gameActionClickListener;
@ -83,6 +84,9 @@ public class MainActivity extends BaseActivity {
initLayout(R.layout.activity_basemain);
mDrawerPullButton.setOnClickListener(v -> drawerLayout.openDrawer(navDrawer));
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
// Set the sustained performance mode for available APIs
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
getWindow().setSustainedPerformanceMode(PREF_SUSTAINED_PERFORMANCE);
@ -110,7 +114,7 @@ public class MainActivity extends BaseActivity {
optionListener = MCOptionUtils::getMcScale;
MCOptionUtils.addMCOptionListener(optionListener);
mControlLayout = findViewById(R.id.main_control_layout);
mControlLayout.setModifiable(false);
try {
mControlLayout.loadLayout(LauncherPreferences.PREF_DEFAULTCTRL_PATH);
@ -131,14 +135,14 @@ public class MainActivity extends BaseActivity {
protected void initLayout(int resId) {
setContentView(resId);
bindViews();
try {
Logger.getInstance().reset();
// FIXME: is it safe fot multi thread?
GLOBAL_CLIPBOARD = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
touchCharInput = findViewById(R.id.mainTouchCharInput);
touchCharInput.setCharacterSender(new LwjglCharSender());
loggerView = findViewById(R.id.mainLoggerView);
mControlLayout = findViewById(R.id.main_control_layout);
LauncherProfiles.update();
@ -175,10 +179,6 @@ public class MainActivity extends BaseActivity {
// Menu
drawerLayout = findViewById(R.id.main_drawer_options);
navDrawer = findViewById(R.id.main_navigation_view);
gameActionArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.menu_ingame));
gameActionClickListener = (parent, view, position, id) -> {
switch(position) {
@ -205,10 +205,10 @@ public class MainActivity extends BaseActivity {
navDrawer.setAdapter(gameActionArrayAdapter);
navDrawer.setOnItemClickListener(gameActionClickListener);
touchpad = findViewById(R.id.main_touchpad);
this.minecraftGLView = findViewById(R.id.main_game_render_view);
this.drawerLayout.closeDrawers();
minecraftGLView.setSurfaceReadyListener(() -> {
@ -375,9 +375,7 @@ public class MainActivity extends BaseActivity {
}
public void leaveCustomControls() {
if(this instanceof MainActivity) {
try {
mControlLayout.hideAllHandleViews();
mControlLayout.loadLayout((CustomControls)null);
mControlLayout.setModifiable(false);
System.gc();
@ -386,7 +384,6 @@ public class MainActivity extends BaseActivity {
Tools.showError(this,e);
}
//((MainActivity) this).mControlLayout.loadLayout((CustomControls)null);
}
navDrawer.setAdapter(gameActionArrayAdapter);
navDrawer.setOnItemClickListener(gameActionClickListener);
isInEditor = false;
@ -509,4 +506,16 @@ public class MainActivity extends BaseActivity {
}
});
}
private void bindViews(){
mControlLayout = findViewById(R.id.main_control_layout);
minecraftGLView = findViewById(R.id.main_game_render_view);
touchpad = findViewById(R.id.main_touchpad);
drawerLayout = findViewById(R.id.main_drawer_options);
navDrawer = findViewById(R.id.main_navigation_view);
loggerView = findViewById(R.id.mainLoggerView);
mControlLayout = findViewById(R.id.main_control_layout);
touchCharInput = findViewById(R.id.mainTouchCharInput);
mDrawerPullButton = findViewById(R.id.drawer_button);
}
}

View file

@ -29,6 +29,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
import net.kdt.pojavlaunch.utils.MathUtils;
import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad;
@ -207,6 +209,9 @@ public class MinecraftGLSurface extends View {
*/
@Override
public boolean onTouchEvent(MotionEvent e) {
// Kinda need to send this back to the layout
if(((ControlLayout)getParent()).getModifiable()) return false;
// Looking for a mouse to handle, won't have an effect if no mouse exists.
for (int i = 0; i < e.getPointerCount(); i++) {
if(e.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE && e.getToolType(i) != MotionEvent.TOOL_TYPE_STYLUS ) continue;

View file

@ -9,10 +9,13 @@ import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.core.math.MathUtils;
import net.kdt.pojavlaunch.Tools;
import top.defaults.checkerboarddrawable.CheckerboardDrawable;
@ -26,7 +29,7 @@ public class AlphaView extends View {
private int mSelectedAlpha;
private float mAlphaDiv; // for quick pos->alpha multiplication
private float mScreenDiv; // for quick alpha->pos multiplication
private float mHeightThird; // 1/3 of the view size for cursor
private float mWidthThird; // 1/3 of the view size for cursor
public AlphaView(Context ctx, AttributeSet attrs) {
super(ctx,attrs);
mBlackPaint = new Paint();
@ -46,9 +49,7 @@ public class AlphaView extends View {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
mSelectedAlpha = (int) (mAlphaDiv * event.getX());
if(mSelectedAlpha < 0) mSelectedAlpha = 0;
if(mSelectedAlpha > 0xff) mSelectedAlpha = 0xff;
mSelectedAlpha = (int) MathUtils.clamp(mAlphaDiv * event.getY(), 0, 0xff);
if(mAlphaSelectionListener != null) mAlphaSelectionListener.onAlphaSelected(mSelectedAlpha);
invalidate();
return true;
@ -58,11 +59,10 @@ public class AlphaView extends View {
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
mViewSize.right = w;
mViewSize.bottom = h;
float h2 = mViewSize.bottom / 2f;
mShaderPaint.setShader(new LinearGradient(0,h2,w,h2, 0, Color.BLACK, Shader.TileMode.REPEAT));
mAlphaDiv = 255f / mViewSize.right;
mScreenDiv = mViewSize.right / 255f;
mHeightThird = mViewSize.bottom / 3f;
mShaderPaint.setShader(new LinearGradient(0,0,0,h, 0, Color.WHITE, Shader.TileMode.REPEAT));
mAlphaDiv = 255f / mViewSize.bottom;
mScreenDiv = mViewSize.bottom / 255f;
mWidthThird = mViewSize.right / 3f;
}
@Override
@ -70,7 +70,7 @@ public class AlphaView extends View {
mCheckerboardDrawable.draw(canvas);
canvas.drawRect(mViewSize, mShaderPaint);
float linePos = mSelectedAlpha * mScreenDiv;
canvas.drawLine(linePos, 0 ,linePos, mHeightThird, mBlackPaint);
canvas.drawLine(linePos, mHeightThird * 2 ,linePos, mViewSize.bottom, mBlackPaint);
canvas.drawLine(0, linePos , mWidthThird, linePos, mBlackPaint);
canvas.drawLine(mWidthThird * 2, linePos, getRight(),linePos, mBlackPaint);
}
}

View file

@ -8,63 +8,68 @@ 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 androidx.constraintlayout.widget.ConstraintLayout;
import net.kdt.pojavlaunch.R;
public class ColorSelector 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 final AlertDialog mDialog;
private ColorSelectionListener mColorSelectionListener;
private float[] mHueTemplate = new float[] {0,1,1};
private float[] mHsvSelected = new float[] {360,1,1};
private final float[] mHueTemplate = new float[] {0,1,1};
private final float[] mHsvSelected = new float[] {360,1,1};
private int mAlphaSelected = 0xff;
private ColorStateList mTextColors;
private final 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, ColorSelectionListener colorSelectionListener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
View view = LayoutInflater.from(context).inflate(R.layout.dialog_color_selector,null);
mHueView = view.findViewById(R.id.color_selector_hue_view);
mLuminosityIntensityView = view.findViewById(R.id.color_selector_rectangle_view);
mAlphaView = view.findViewById(R.id.color_selector_alpha_view);
mColorView = view.findViewById(R.id.color_selector_color_view);
mTextView = view.findViewById(R.id.color_selector_hex_edit);
public ColorSelector(Context context, ViewGroup parent, @Nullable ColorSelectionListener 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);
runColor(Color.RED);
mHueView.setHueSelectionListener(this);
mLuminosityIntensityView.setRectSelectionListener(this);
mAlphaView.setAlphaSelectionListener(this);
mTextView.addTextChangedListener(this);
mTextColors = mTextView.getTextColors();
builder.setView(view);
builder.setPositiveButton(android.R.string.ok,(dialog,which)->{
if (mColorSelectionListener != null) {
mColorSelectionListener.onColorSelected(Color.HSVToColor(mAlphaSelected, mHsvSelected));
}
});
builder.setNegativeButton(android.R.string.cancel, ((dialog, which) -> {}));
mColorSelectionListener = colorSelectionListener;
mDialog = builder.create();
parent.addView(mRootView);
}
/** @return The root view, mainly for position manipulation purposes */
public View getRootView(){
return mRootView;
}
/**
* Shows the color selector with the default (red) color selected.
*/
public void show() {
runColor(Color.RED);
dispatchColorChange();
mDialog.show();
show(Color.RED);
}
/**
@ -74,7 +79,6 @@ public class ColorSelector implements HueSelectionListener, RectangleSelectionLi
public void show(int previousColor) {
runColor(previousColor); // initialize
dispatchColorChange(); // set the hex text
mDialog.show();
}
@Override
@ -113,9 +117,10 @@ public class ColorSelector implements HueSelectionListener, RectangleSelectionLi
mColorView.setColor(color);
mWatch = false;
mTextView.setText(String.format("%08X",color));
notifyColorSelector(color);
}
//IUO: sets all Views to render the desired color. Used for initilaization and HEX color input
//IUO: sets all Views to render the desired color. Used for initialization and HEX color input
protected void runColor(int color) {
Color.RGBToHSV(Color.red(color), Color.green(color), Color.blue(color), mHsvSelected);
mHueTemplate[0] = mHsvSelected[0];
@ -123,7 +128,7 @@ public class ColorSelector implements HueSelectionListener, RectangleSelectionLi
mLuminosityIntensityView.setColor(Color.HSVToColor(mHueTemplate), false);
mLuminosityIntensityView.setLuminosityIntensity(mHsvSelected[2], mHsvSelected[1]);
mAlphaSelected = Color.alpha(color);
mAlphaView.setAlpha(mAlphaSelected);
mAlphaView.setAlpha(mAlphaEnabled ? mAlphaSelected : 255);
mColorView.setColor(color);
}
@ -147,4 +152,18 @@ public class ColorSelector implements HueSelectionListener, RectangleSelectionLi
mWatch = true;
}
}
public void setColorSelectionListener(ColorSelectionListener listener){
mColorSelectionListener = listener;
}
public void setAlphaEnabled(boolean alphaEnabled){
mAlphaEnabled = alphaEnabled;
mAlphaView.setVisibility(alphaEnabled ? View.VISIBLE : View.GONE);
}
private void notifyColorSelector(int color){
if(mColorSelectionListener != null)
mColorSelectionListener.onColorSelected(color);
}
}

View file

@ -21,11 +21,11 @@ public class HueView extends View {
private Bitmap mGamma;
private HueSelectionListener mHueSelectionListener;
private float mSelectionHue;
private float mWidthHueRatio;
private float mHueWidthRatio;
private float mHeightHueRatio;
private float mHueHeightRatio;
private float mWidth;
private float mHeight;
private float mHeightThird;
private float mWidthThird;
public HueView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
blackPaint.setColor(Color.BLACK);
@ -43,7 +43,7 @@ public class HueView extends View {
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
mSelectionHue = event.getX() * mWidthHueRatio;
mSelectionHue = event.getY() * mHeightHueRatio;
invalidate();
if(mHueSelectionListener != null) mHueSelectionListener.onHueSelected(mSelectionHue);
return true;
@ -52,16 +52,16 @@ public class HueView extends View {
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mGamma, 0, 0 ,null);
float linePos = mSelectionHue * mHueWidthRatio;
canvas.drawLine(linePos, 0 ,linePos, mHeightThird, blackPaint);
canvas.drawLine(linePos, mHeightThird * 2 ,linePos, mHeight, blackPaint);
float linePos = mSelectionHue * mHueHeightRatio;
canvas.drawLine(0, linePos , mWidthThird, linePos, blackPaint);
canvas.drawLine( mWidthThird * 2 ,linePos, mWidth, linePos, blackPaint);
}
@Override
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
mWidth = w;
mHeight = h;
mHeightThird = mHeight / 3;
mWidthThird = mWidth / 3;
regenerateGammaBitmap();
}
@ -71,13 +71,13 @@ public class HueView extends View {
mGamma = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Paint paint = new Paint();
Canvas canvas = new Canvas(mGamma);
mWidthHueRatio = 360/ mWidth;
mHueWidthRatio = mWidth / 360;
mHeightHueRatio = 360/ mHeight;
mHueHeightRatio = mHeight / 360;
float[] hsvFiller = new float[] {0, 1, 1};
for(float i = 0; i < mWidth; i++) {
hsvFiller[0] = i * mWidthHueRatio;
for(float i = 0; i < mHeight; i++) {
hsvFiller[0] = i * mHeightHueRatio;
paint.setColor(Color.HSVToColor(hsvFiller));
canvas.drawLine(i,0,i, mHeight,paint);
canvas.drawLine(0,i,mWidth, i,paint);
}
}
}

View file

@ -1,7 +1,11 @@
package net.kdt.pojavlaunch.customcontrols;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
import android.content.*;
import android.util.*;
import android.view.*;
import android.view.inputmethod.InputMethodManager;
import android.widget.*;
import com.google.gson.*;
import java.io.*;
@ -12,16 +16,24 @@ import java.util.HashMap;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
import net.kdt.pojavlaunch.customcontrols.handleview.HandleView;
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.prefs.*;
public class ControlLayout extends FrameLayout {
protected CustomControls mLayout;
private boolean mModifiable;
private boolean mModifiable = false;
private CustomControlsActivity mActivity;
private boolean mControlVisible = false;
private EditControlPopup mControlPopup = null;
private ControlHandleView mHandleView;
public ActionRow actionRow = null;
public ControlLayout(Context ctx) {
super(ctx);
}
@ -30,25 +42,22 @@ public class ControlLayout extends FrameLayout {
super(ctx, attrs);
}
public void hideAllHandleViews() {
for(ControlButton button : getButtonChildren()){
HandleView hv = button.getHandleView();
if(hv != null) hv.hide();
}
}
public void loadLayout(String jsonPath) throws IOException, JsonSyntaxException {
CustomControls layout = LayoutConverter.loadAndConvertIfNecessary(jsonPath);
if(layout != null) {
loadLayout(layout);
}else{
throw new IOException("Unsupported control layout version");
return;
}
throw new IOException("Unsupported control layout version");
}
public void loadLayout(CustomControls controlLayout) {
if (mModifiable)
hideAllHandleViews();
if(actionRow == null){
actionRow = new ActionRow(getContext());
addView(actionRow);
}
removeAllButtons();
if(mLayout != null) {
@ -73,9 +82,6 @@ public class ControlLayout extends FrameLayout {
for(ControlDrawerData drawerData : controlLayout.mDrawerDataList){
ControlDrawer drawer = addDrawerView(drawerData);
if(mModifiable) drawer.areButtonsVisible = true;
}
mLayout.scaledAt = LauncherPreferences.PREF_BUTTONSIZE;
@ -91,7 +97,7 @@ public class ControlLayout extends FrameLayout {
private void addControlView(ControlData controlButton) {
final ControlButton view = new ControlButton(this, controlButton);
view.setModifiable(mModifiable);
if (!mModifiable) {
view.setAlpha(view.getProperties().opacity);
view.setFocusable(false);
@ -115,7 +121,7 @@ public class ControlLayout extends FrameLayout {
private ControlDrawer addDrawerView(ControlDrawerData drawerData){
final ControlDrawer view = new ControlDrawer(this,drawerData == null ? mLayout.mDrawerDataList.get(mLayout.mDrawerDataList.size()-1) : drawerData);
view.setModifiable(mModifiable);
if (!mModifiable) {
view.setAlpha(view.getProperties().opacity);
view.setFocusable(false);
@ -138,9 +144,9 @@ public class ControlLayout extends FrameLayout {
addSubView(drawer, drawer.getDrawerData().buttonProperties.get(drawer.getDrawerData().buttonProperties.size()-1 ));
}
public void addSubView(ControlDrawer drawer, ControlData controlButton){
private void addSubView(ControlDrawer drawer, ControlData controlButton){
final ControlSubButton view = new ControlSubButton(this, controlButton, drawer);
view.setModifiable(mModifiable);
if (!mModifiable) {
view.setAlpha(view.getProperties().opacity);
view.setFocusable(false);
@ -155,9 +161,10 @@ public class ControlLayout extends FrameLayout {
setModified(true);
}
private void removeAllButtons() {
for(View v : getButtonChildren()){
removeView(v);
for(ControlInterface button : getButtonChildren()){
removeView(button.getControlView());
}
System.gc();
@ -165,37 +172,6 @@ public class ControlLayout extends FrameLayout {
//because if frames will slowly go down after many control changes it will be warm and bad
}
public void removeControlButton(ControlButton controlButton) {
mLayout.mControlDataList.remove(controlButton.getProperties());
controlButton.setVisibility(View.GONE);
removeView(controlButton);
setModified(true);
}
public void removeControlDrawer(ControlDrawer controlDrawer){
for(ControlSubButton subButton : controlDrawer.buttons){
subButton.setVisibility(GONE);
removeView(subButton);
}
mLayout.mDrawerDataList.remove(controlDrawer.getDrawerData());
controlDrawer.setVisibility(GONE);
removeView(controlDrawer);
setModified(true);
}
public void removeControlSubButton(ControlSubButton subButton){
subButton.parentDrawer.drawerData.buttonProperties.remove(subButton.getProperties());
subButton.parentDrawer.buttons.remove(subButton);
subButton.parentDrawer.syncButtons();
subButton.setVisibility(GONE);
removeView(subButton);
}
public void saveLayout(String path) throws Exception {
mLayout.save(path);
setModified(false);
@ -214,22 +190,27 @@ public class ControlLayout extends FrameLayout {
return mLayout.scaledAt;
}
public CustomControls getLayout(){
return mLayout;
}
public void setControlVisible(boolean isVisible) {
if (mModifiable) return; // Not using on custom controls activity
mControlVisible = isVisible;
for(ControlButton button : getButtonChildren()){
for(ControlInterface button : getButtonChildren()){
button.setVisible(isVisible);
}
}
public void setModifiable(boolean isModifiable) {
mModifiable = isModifiable;
for(ControlButton button : getButtonChildren()){
button.setModifiable(isModifiable);
if (!isModifiable)
button.setAlpha(button.getProperties().opacity);
if(isModifiable){
}else {
if(mModifiable)
removeEditWindow();
}
mModifiable = isModifiable;
}
public boolean getModifiable(){
@ -241,27 +222,74 @@ public class ControlLayout extends FrameLayout {
}
public ArrayList<ControlButton> getButtonChildren(){
ArrayList<ControlButton> children = new ArrayList<>();
public ArrayList<ControlInterface> getButtonChildren(){
ArrayList<ControlInterface> children = new ArrayList<>();
for(int i=0; i<getChildCount(); ++i){
View v = getChildAt(i);
if(v instanceof ControlButton)
children.add(((ControlButton) v));
if(v instanceof ControlInterface)
children.add(((ControlInterface) v));
}
return children;
}
public void refreshControlButtonPositions(){
for(ControlButton button : getButtonChildren()){
for(ControlInterface button : getButtonChildren()){
button.setDynamicX(button.getProperties().dynamicX);
button.setDynamicY(button.getProperties().dynamicY);
}
}
HashMap<View, ControlButton> mapTable = new HashMap<>();
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
if(child instanceof ControlInterface){
mControlPopup.disappearColor();
mControlPopup.disappear();
}
}
/**
* Load the layout if needed, and pass down the burden of filling values
* to the button at hand.
*/
public void editControlButton(ControlInterface button){
if(mControlPopup == 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);
post(() -> editControlButton(button));
return;
}
mControlPopup.internalChanges = true;
mControlPopup.setCurrentlyEditedButton(button);
button.loadEditValues(mControlPopup);
mControlPopup.internalChanges = false;
mControlPopup.appear(button.getControlView().getX() + button.getControlView().getWidth()/2f < currentDisplayMetrics.widthPixels/2f);
mControlPopup.disappearColor();
if(mHandleView == null){
mHandleView = new ControlHandleView(getContext());
addView(mHandleView);
}
mHandleView.setControlButton(button);
//mHandleView.show();
}
/** Swap the panel if the button position requires it */
public void adaptPanelPosition(){
if(mControlPopup != null)
mControlPopup.adaptPanelPosition();
}
HashMap<View, ControlInterface> mapTable = new HashMap<>();
//While this is called onTouch, this should only be called from a ControlButton.
public boolean onTouch(View v, MotionEvent ev) {
ControlButton lastControlButton = mapTable.get(v);
ControlInterface lastControlButton = mapTable.get(v);
//Check if the action is cancelling, reset the lastControl button associated to the view
if(ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_CANCEL){
@ -274,8 +302,8 @@ public class ControlLayout extends FrameLayout {
//Optimization pass to avoid looking at all children again
if(lastControlButton != null){
if( ev.getRawX() > lastControlButton.getX() && ev.getRawX() < lastControlButton.getX() + lastControlButton.getWidth() &&
ev.getRawY() > lastControlButton.getY() && ev.getRawY() < lastControlButton.getY() + lastControlButton.getHeight()){
if( ev.getRawX() > lastControlButton.getControlView().getX() && ev.getRawX() < lastControlButton.getControlView().getX() + lastControlButton.getControlView().getWidth() &&
ev.getRawY() > lastControlButton.getControlView().getY() && ev.getRawY() < lastControlButton.getControlView().getY() + lastControlButton.getControlView().getHeight()){
return true;
}
}
@ -285,11 +313,11 @@ public class ControlLayout extends FrameLayout {
mapTable.put(v, null);
//Look for another SWIPEABLE button
for(ControlButton button : getButtonChildren()){
for(ControlInterface button : getButtonChildren()){
if(!button.getProperties().isSwipeable) continue;
if( ev.getRawX() > button.getX() && ev.getRawX() < button.getX() + button.getWidth() &&
ev.getRawY() > button.getY() && ev.getRawY() < button.getY() + button.getHeight()){
if( ev.getRawX() > button.getControlView().getX() && ev.getRawX() < button.getControlView().getX() + button.getControlView().getWidth() &&
ev.getRawY() > button.getControlView().getY() && ev.getRawY() < button.getControlView().getY() + button.getControlView().getHeight()){
//Press the new key
if(!button.equals(lastControlButton)){
@ -302,4 +330,39 @@ public class ControlLayout extends FrameLayout {
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mModifiable && event.getActionMasked() != MotionEvent.ACTION_UP || mControlPopup == 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()){
actionRow.setFollowedButton(null);
mHandleView.hide();
}
}
return true;
}
public void removeEditWindow() {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE);
// When the input window cannot be hidden, it returns false
imm.hideSoftInputFromWindow(getWindowToken(), 0);
mControlPopup.disappearColor();
mControlPopup.disappear();
actionRow.setFollowedButton(null);
mHandleView.hide();
}
public void save(String path){
try {
mLayout.save(path);
} catch (IOException e) {Log.e("ControlLayout", "Failed to save the layout at:" + path);}
}
}

View file

@ -2,16 +2,11 @@ package net.kdt.pojavlaunch.customcontrols.buttons;
import android.annotation.SuppressLint;
import android.graphics.*;
import android.graphics.drawable.GradientDrawable;
import android.util.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import androidx.core.math.MathUtils;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
import net.kdt.pojavlaunch.customcontrols.handleview.*;
@ -20,123 +15,56 @@ import net.kdt.pojavlaunch.*;
import org.lwjgl.glfw.*;
import static net.kdt.pojavlaunch.LwjglGlfwKeycode.GLFW_KEY_UNKNOWN;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_BUTTONSIZE;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_BOTTOM_OFFSET;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_LEFT_OFFSET;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_RIGHT_OFFSET;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_TOP_OFFSET;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
@SuppressLint("ViewConstructor")
public class ControlButton extends androidx.appcompat.widget.AppCompatButton implements OnLongClickListener {
@SuppressLint({"ViewConstructor", "AppCompatCustomView"})
public class ControlButton extends TextView implements ControlInterface {
private final Paint mRectPaint = new Paint();
protected GestureDetector mGestureDetector;
protected ControlData mProperties;
protected SelectionEndHandleView mHandleView;
protected boolean mModifiable = false;
protected boolean mCanTriggerLongClick = true;
protected boolean mIsToggled = false;
protected boolean mIsPointerOutOfBounds = false;
public ControlButton(ControlLayout layout, ControlData properties) {
super(layout.getContext());
setGravity(Gravity.CENTER);
setAllCaps(true);
setTextColor(Color.WHITE);
setPadding(4, 4, 4, 4);
setOnLongClickListener(this);
//setOnLongClickListener(this);
//When a button is created, the width/height has yet to be processed to fit the scaling.
setProperties(preProcessProperties(properties, layout));
setModified(false);
injectTouchEventBehavior();
injectLayoutParamBehavior();
}
public HandleView getHandleView() {
return mHandleView;
}
@Override
public View getControlView() {return this;}
public ControlData getProperties() {
return mProperties;
}
public void setProperties(ControlData properties) {
setProperties(properties, true);
}
public ControlData preProcessProperties(ControlData properties, ControlLayout layout){
//When a button is created, properties have to be modified to fit the screen.
//Size
properties.setWidth(properties.getWidth() / layout.getLayoutScale() * PREF_BUTTONSIZE);
properties.setHeight(properties.getHeight() / layout.getLayoutScale() * PREF_BUTTONSIZE);
//Visibility
properties.isHideable = !properties.containsKeycode(ControlData.SPECIALBTN_TOGGLECTRL) && !properties.containsKeycode(ControlData.SPECIALBTN_VIRTUALMOUSE);
return properties;
}
public void setProperties(ControlData properties, boolean changePos) {
mProperties = properties;
ControlInterface.super.setProperties(properties, changePos);
if(mProperties.isToggle){
if (mProperties.isToggle) {
//For the toggle layer
final TypedValue value = new TypedValue();
getContext().getTheme().resolveAttribute(R.attr.colorAccent, value, true);
mRectPaint.setColor(value.data);
mRectPaint.setAlpha(128);
}else{
} else {
mRectPaint.setColor(Color.WHITE);
mRectPaint.setAlpha(60);
}
setText(properties.name);
if (changePos) {
setX(properties.insertDynamicPos(mProperties.dynamicX));
setY(properties.insertDynamicPos(mProperties.dynamicY));
}
setLayoutParams(new FrameLayout.LayoutParams((int) properties.getWidth(), (int) properties.getHeight() ));
}
public void setBackground(){
GradientDrawable gd = new GradientDrawable();
gd.setColor(mProperties.bgColor);
gd.setStroke(computeStrokeWidth(mProperties.strokeWidth), mProperties.strokeColor);
gd.setCornerRadius(computeCornerRadius(mProperties.cornerRadius));
setBackground(gd);
}
public void setModifiable(boolean isModifiable) {
mModifiable = isModifiable;
}
private void setModified(boolean modified) {
if (getParent() != null)
((ControlLayout) getParent()).setModified(modified);
}
@Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
super.setLayoutParams(params);
mProperties.setWidth(params.width);
mProperties.setHeight(params.height);
setBackground();
// Re-calculate position
if(!mProperties.isDynamicBtn){
setX(getX());
setY(getY());
}else {
setX(mProperties.insertDynamicPos(mProperties.dynamicX));
setY(mProperties.insertDynamicPos(mProperties.dynamicY));
}
setModified(true);
}
public void setVisible(boolean isVisible){
@ -144,113 +72,6 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
setVisibility(isVisible ? VISIBLE : GONE);
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
setWillNotDraw(visibility == GONE);
}
@Override
public void setX(float x) {
// We have to account for control offset preference
if(x + (mProperties.getWidth()/2f) > CallbackBridge.physicalWidth/2f){
x -= PREF_CONTROL_RIGHT_OFFSET;
}else{
x += PREF_CONTROL_LEFT_OFFSET;
}
super.setX(x);
setModified(true);
}
@Override
public void setY(float y) {
// We have to account for control offset preference
if(y - PREF_CONTROL_TOP_OFFSET + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
y -= PREF_CONTROL_BOTTOM_OFFSET;
}else{
y += PREF_CONTROL_TOP_OFFSET;
}
super.setY(y);
setModified(true);
}
@Override
public float getX() {
float x = super.getX();
// We have to account for control offset preference
if(x + (mProperties.getWidth()/2f) > (CallbackBridge.physicalWidth)/2f){
x += PREF_CONTROL_RIGHT_OFFSET;
}else{
x -= PREF_CONTROL_LEFT_OFFSET;
}
return x;
}
@Override
public float getY(){
// We have to account for control offset preference
float y = super.getY();
if(y + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
y += PREF_CONTROL_BOTTOM_OFFSET;
}else{
y -= PREF_CONTROL_TOP_OFFSET;
}
return y;
}
/**
* Apply the dynamic equation on the x axis.
* @param dynamicX The equation to compute the position from
*/
public void setDynamicX(String dynamicX){
mProperties.dynamicX = dynamicX;
setX(mProperties.insertDynamicPos(dynamicX));
}
/**
* Apply the dynamic equation on the y axis.
* @param dynamicY The equation to compute the position from
*/
public void setDynamicY(String dynamicY){
mProperties.dynamicY = dynamicY;
setY(mProperties.insertDynamicPos(dynamicY));
}
/**
* Generate a dynamic equation from an absolute position, used to scale properly across devices
* @param x The absolute position on the horizontal axis
* @return The equation as a String
*/
public String generateDynamicX(float x){
if(x + (mProperties.getWidth()/2f) > CallbackBridge.physicalWidth/2f){
return (x + mProperties.getWidth()) / CallbackBridge.physicalWidth + " * ${screen_width} - ${width}";
}else{
return x / CallbackBridge.physicalWidth + " * ${screen_width}";
}
}
/**
* Generate a dynamic equation from an absolute position, used to scale properly across devices
* @param y The absolute position on the vertical axis
* @return The equation as a String
*/
public String generateDynamicY(float y){
if(y + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
return (y + mProperties.getHeight()) / CallbackBridge.physicalHeight + " * ${screen_height} - ${height}";
}else{
return y / CallbackBridge.physicalHeight + " * ${screen_height}";
}
}
public void updateProperties() {
setProperties(mProperties);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
@ -258,63 +79,55 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), mProperties.cornerRadius, mProperties.cornerRadius, mRectPaint);
}
@Override
public boolean onLongClick(View v) {
if (mCanTriggerLongClick && mModifiable) {
//Instantiate on need only
if(mHandleView == null) mHandleView = new SelectionEndHandleView(this);
if (mHandleView.isShowing()) {
mHandleView.hide();
} else {
if (getParent() != null) {
((ControlLayout) getParent()).hideAllHandleViews();
public void loadEditValues(EditControlPopup editControlPopup){
editControlPopup.loadValues(getProperties());
}
try {
mHandleView.show(this);
} catch (Throwable th) {
th.printStackTrace();
}
}
/** Add another instance of the ControlButton to the parent layout */
public void cloneButton(){
ControlData cloneData = new ControlData(getProperties());
cloneData.dynamicX = "0.5 * ${screen_width}";
cloneData.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) getParent()).addControlButton(cloneData);
}
return mCanTriggerLongClick;
/** Remove any trace of this button from the layout */
public void removeButton() {
getControlLayoutParent().getLayout().mControlDataList.remove(getProperties());
getControlLayoutParent().removeView(this);
}
protected float downX, downY;
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!mModifiable){
mCanTriggerLongClick = false;
switch (event.getActionMasked()){
case MotionEvent.ACTION_MOVE:
//Send the event to be taken as a mouse action
if(mProperties.passThruEnabled && CallbackBridge.isGrabbing()){
MinecraftGLSurface v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
if(getProperties().passThruEnabled && CallbackBridge.isGrabbing()){
MinecraftGLSurface v = getControlLayoutParent().findViewById(R.id.main_game_render_view);
if (v != null) v.dispatchTouchEvent(event);
}
//If out of bounds
if(event.getX() < getLeft() || event.getX() > getRight() ||
event.getY() < getTop() || event.getY() > getBottom()){
if(mProperties.isSwipeable && !mIsPointerOutOfBounds){
if(event.getX() < getControlView().getLeft() || event.getX() > getControlView().getRight() ||
event.getY() < getControlView().getTop() || event.getY() > getControlView().getBottom()){
if(getProperties().isSwipeable && !mIsPointerOutOfBounds){
//Remove keys
if(!triggerToggle()) {
sendKeyPresses(false);
}
}
mIsPointerOutOfBounds = true;
((ControlLayout) getParent()).onTouch(this, event);
getControlLayoutParent().onTouch(this, event);
break;
}
//Else if we now are in bounds
if(mIsPointerOutOfBounds) {
((ControlLayout) getParent()).onTouch(this, event);
getControlLayoutParent().onTouch(this, event);
//RE-press the button
if(mProperties.isSwipeable && !mProperties.isToggle){
if(getProperties().isSwipeable && !getProperties().isToggle){
sendKeyPresses(true);
}
}
@ -323,7 +136,7 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
case MotionEvent.ACTION_DOWN: // 0
case MotionEvent.ACTION_POINTER_DOWN: // 5
if(!mProperties.isToggle){
if(!getProperties().isToggle){
sendKeyPresses(true);
}
break;
@ -331,11 +144,11 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
case MotionEvent.ACTION_UP: // 1
case MotionEvent.ACTION_CANCEL: // 3
case MotionEvent.ACTION_POINTER_UP: // 6
if(mProperties.passThruEnabled){
MinecraftGLSurface v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
if(getProperties().passThruEnabled){
MinecraftGLSurface v = getControlLayoutParent().findViewById(R.id.main_game_render_view);
if (v != null) v.dispatchTouchEvent(event);
}
if(mIsPointerOutOfBounds) ((ControlLayout) getParent()).onTouch(this, event);
if(mIsPointerOutOfBounds) getControlLayoutParent().onTouch(this, event);
mIsPointerOutOfBounds = false;
if(!triggerToggle()) {
@ -346,152 +159,11 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
default:
return false;
}
return true;
}
/* If the button can be modified/moved */
//Instantiate the gesture detector only when needed
if(mGestureDetector == null) mGestureDetector = new GestureDetector(getContext(), new SingleTapConfirm());
if (mGestureDetector.onTouchEvent(event)) {
mCanTriggerLongClick = true;
onLongClick(this);
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
mCanTriggerLongClick = true;
downX = event.getRawX() - getX();
downY = event.getRawY() - getY();
break;
case MotionEvent.ACTION_MOVE:
mCanTriggerLongClick = false;
if (!mProperties.isDynamicBtn) {
snapAndAlign(
MathUtils.clamp(event.getRawX() - downX, 0, CallbackBridge.physicalWidth - getWidth()),
MathUtils.clamp(event.getRawY() - downY, 0, CallbackBridge.physicalHeight - getHeight())
);
}
break;
}
return super.onTouchEvent(event);
}
/**
* Passe a series of checks to determine if the ControlButton is available to be snapped on.
*
* @param button The button to check
* @return whether or not the button
*/
protected boolean canSnap(ControlButton button){
float MIN_DISTANCE = Tools.dpToPx(8);
if(button == this) return false;
if(net.kdt.pojavlaunch.utils.MathUtils.dist(
button.getX() + button.getWidth()/2f,
button.getY() + button.getHeight()/2f,
getX() + getWidth()/2f,
getY() + getHeight()/2f) > Math.max(button.getWidth()/2f + getWidth()/2f, button.getHeight()/2f + getHeight()/2f) + MIN_DISTANCE) return false;
return true;
}
/**
* Try to snap, then align to neighboring buttons, given the provided coordinates.
* The new position is automatically applied to the View,
* regardless of if the View snapped or not.
*
* The new position is always dynamic, thus replacing previous dynamic positions
*
* @param x Coordinate on the x axis
* @param y Coordinate on the y axis
*/
protected void snapAndAlign(float x, float y){
float MIN_DISTANCE = Tools.dpToPx(8);
String dynamicX = generateDynamicX(x);
String dynamicY = generateDynamicY(y);
setX(x);
setY(y);
for(ControlButton button : ((ControlLayout) getParent()).getButtonChildren()){
//Step 1: Filter unwanted buttons
if(!canSnap(button)) continue;
//Step 2: Get Coordinates
float button_top = button.getY();
float button_bottom = button_top + button.getHeight();
float button_left = button.getX();
float button_right = button_left + button.getWidth();
float top = getY();
float bottom = getY() + getHeight();
float left = getX();
float right = getX() + getWidth();
//Step 3: For each axis, we try to snap to the nearest
if(Math.abs(top - button_bottom) < MIN_DISTANCE){ // Bottom snap
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " + ${margin}" ;
}else if(Math.abs(button_top - bottom) < MIN_DISTANCE){ //Top snap
dynamicY = applySize(button.getProperties().dynamicY, button) + " - ${height} - ${margin}";
}
if(!dynamicY.equals(generateDynamicY(getY()))){ //If we snapped
if(Math.abs(button_left - left) < MIN_DISTANCE){ //Left align snap
dynamicX = applySize(button.getProperties().dynamicX, button);
}else if(Math.abs(button_right - right) < MIN_DISTANCE){ //Right align snap
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " - ${width}";
}
}
if(Math.abs(button_left - right) < MIN_DISTANCE){ //Left snap
dynamicX = applySize(button.getProperties().dynamicX, button) + " - ${width} - ${margin}";
}else if(Math.abs(left - button_right) < MIN_DISTANCE){ //Right snap
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " + ${margin}";
}
if(!dynamicX.equals(generateDynamicX(getX()))){ //If we snapped
if(Math.abs(button_top - top) < MIN_DISTANCE){ //Top align snap
dynamicY = applySize(button.getProperties().dynamicY, button);
}else if(Math.abs(button_bottom - bottom) < MIN_DISTANCE){ //Bottom align snap
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " - ${height}";
}
}
}
setDynamicX(dynamicX);
setDynamicY(dynamicY);
}
/**
* Do a pre-conversion of an equation using values from a button,
* so the variables can be used for another button
*
* Internal use only.
* @param equation The dynamic position as a String
* @param button The button to get the values from.
* @return The pre-processed equation as a String.
*/
private static String applySize(String equation, ControlButton button){
return equation
.replace("${right}", "(${screen_width} - ${width})")
.replace("${bottom}","(${screen_height} - ${height})")
.replace("${height}", "(px(" + Tools.pxToDp(button.getProperties().getHeight()) + ") /" + PREF_BUTTONSIZE + " * ${preferred_scale})")
.replace("${width}", "(px(" + Tools.pxToDp(button.getProperties().getWidth()) + ") / " + PREF_BUTTONSIZE + " * ${preferred_scale})");
}
public int computeStrokeWidth(float widthInPercent){
float maxSize = Math.max(mProperties.getWidth(), mProperties.getHeight());
return (int)((maxSize/2) * (widthInPercent/100));
}
public float computeCornerRadius(float radiusInPercent){
float minSize = Math.min(mProperties.getWidth(), mProperties.getHeight());
return (minSize/2) * (radiusInPercent/100);
}
public boolean triggerToggle(){
//returns true a the toggle system is triggered
@ -552,4 +224,8 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
}
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
}

View file

@ -2,12 +2,14 @@ package net.kdt.pojavlaunch.customcontrols.buttons;
import android.annotation.SuppressLint;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
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 java.util.ArrayList;
@ -19,7 +21,7 @@ public class ControlDrawer extends ControlButton {
public ArrayList<ControlSubButton> buttons;
public ControlDrawerData drawerData;
public ControlLayout layout;
public ControlLayout parentLayout;
public boolean areButtonsVisible;
@ -27,19 +29,19 @@ public class ControlDrawer extends ControlButton {
super(layout, drawerData.properties);
buttons = new ArrayList<>(drawerData.buttonProperties.size());
this.layout = layout;
this.parentLayout = layout;
this.drawerData = drawerData;
areButtonsVisible = layout.getModifiable();
}
public void addButton(ControlData properties){
addButton(new ControlSubButton(layout, properties, this));
addButton(new ControlSubButton(parentLayout, properties, this));
}
public void addButton(ControlSubButton button){
buttons.add(button);
setControlButtonVisibility(button, mModifiable || areButtonsVisible);
setControlButtonVisibility(button, areButtonsVisible);
syncButtons();
}
@ -56,10 +58,10 @@ public class ControlDrawer extends ControlButton {
//Syncing stuff
private void alignButtons(){
if(buttons == null) return;
if(drawerData.orientation == ControlDrawerData.Orientation.FREE) return;
for(int i=0; i < buttons.size(); ++i){
for(int i = 0; i < buttons.size(); ++i){
switch (drawerData.orientation){
case RIGHT:
buttons.get(i).setDynamicX(generateDynamicX(getX() + (drawerData.properties.getWidth() + Tools.dpToPx(2))*(i+1) ));
@ -107,7 +109,7 @@ public class ControlDrawer extends ControlButton {
* @param button The button to look for
* @return Whether the button is in the buttons list of the drawer.
*/
public boolean containsChild(ControlButton button){
public boolean containsChild(ControlInterface button){
for(ControlButton childButton : buttons){
if (childButton == button) return true;
}
@ -129,7 +131,7 @@ public class ControlDrawer extends ControlButton {
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!mModifiable){
if(!getControlLayoutParent().getModifiable()){
switch (event.getActionMasked()){
case MotionEvent.ACTION_UP: // 1
case MotionEvent.ACTION_POINTER_UP: // 6
@ -142,10 +144,6 @@ public class ControlDrawer extends ControlButton {
return super.onTouchEvent(event);
}
@Override
protected boolean canSnap(ControlButton button) {
return super.canSnap(button) && !containsChild(button);
}
@Override
public void setX(float x) {
@ -165,8 +163,39 @@ public class ControlDrawer extends ControlButton {
syncButtons();
}
@Override
public boolean canSnap(ControlInterface button) {
boolean result = super.canSnap(button);
return result && !containsChild(button);
}
//Getters
public ControlDrawerData getDrawerData() {
return drawerData;
}
@Override
public void loadEditValues(EditControlPopup editControlPopup) {
editControlPopup.loadValues(drawerData);
}
@Override
public void cloneButton() {
ControlDrawerData cloneData = new ControlDrawerData(getDrawerData());
cloneData.properties.dynamicX = "0.5 * ${screen_width}";
cloneData.properties.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) getParent()).addDrawer(cloneData);
}
@Override
public void removeButton() {
ControlLayout layout = getControlLayoutParent();
for(ControlSubButton subButton : buttons){
layout.removeView(subButton);
}
layout.getLayout().mDrawerDataList.remove(getDrawerData());
layout.removeView(this);
}
}

View file

@ -0,0 +1,345 @@
package net.kdt.pojavlaunch.customcontrols.buttons;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_BUTTONSIZE;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.CallSuper;
import androidx.core.math.MathUtils;
import net.kdt.pojavlaunch.MinecraftGLSurface;
import net.kdt.pojavlaunch.R;
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 org.lwjgl.glfw.CallbackBridge;
/**
* Interface injecting custom behavior to a View.
* Most of the injected behavior is editing behavior,
* sending keys has to be implemented by sub classes.
*/
public interface ControlInterface extends View.OnLongClickListener {
View getControlView();
ControlData getProperties();
/** Remove the button presence from the CustomControl object
* You need to use {getControlParent()} for this.
*/
void removeButton();
/** Duplicate the data of the button and add a view with the duplicated data
* Relies on the ControlLayout for the implementation.
*/
void cloneButton();
void setVisible(boolean isVisible);
void sendKeyPresses(boolean isDown);
/** Load the values and hide non useful forms */
void loadEditValues(EditControlPopup editControlPopup);
default ControlLayout getControlLayoutParent(){
return (ControlLayout) getControlView().getParent();
}
/** Apply conversion steps for when the view is created */
default ControlData preProcessProperties(ControlData properties, ControlLayout layout){
//Size
properties.setWidth(properties.getWidth() / layout.getLayoutScale() * PREF_BUTTONSIZE);
properties.setHeight(properties.getHeight() / layout.getLayoutScale() * PREF_BUTTONSIZE);
//Visibility
properties.isHideable = !properties.containsKeycode(ControlData.SPECIALBTN_TOGGLECTRL) && !properties.containsKeycode(ControlData.SPECIALBTN_VIRTUALMOUSE);
return properties;
}
default void updateProperties() {
setProperties(getProperties());
}
default void setProperties(ControlData properties) {
setProperties(properties, true);
}
/* This function should be overridden to store the properties */
@CallSuper
default void setProperties(ControlData properties, boolean changePos) {
if (changePos) {
getControlView().setX(properties.insertDynamicPos(getProperties().dynamicX));
getControlView().setY(properties.insertDynamicPos(getProperties().dynamicY));
}
// Recycle layout params
ViewGroup.LayoutParams params = getControlView().getLayoutParams();
if(params == null) params = new FrameLayout.LayoutParams((int) properties.getWidth(), (int) properties.getHeight());
params.width = (int) properties.getWidth();
params.height = (int) properties.getHeight();
getControlView().setLayoutParams(params);
}
/** Apply the background according to properties */
default void setBackground(){
GradientDrawable gd = getControlView().getBackground() instanceof GradientDrawable
? (GradientDrawable) getControlView().getBackground()
: new GradientDrawable();
gd.setColor(getProperties().bgColor);
gd.setStroke(computeStrokeWidth(getProperties().strokeWidth), getProperties().strokeColor);
gd.setCornerRadius(computeCornerRadius(getProperties().cornerRadius));
getControlView().setBackground(gd);
}
/**
* Apply the dynamic equation on the x axis.
* @param dynamicX The equation to compute the position from
*/
default void setDynamicX(String dynamicX){
getProperties().dynamicX = dynamicX;
getControlView().setX(getProperties().insertDynamicPos(dynamicX));
}
/**
* Apply the dynamic equation on the y axis.
* @param dynamicY The equation to compute the position from
*/
default void setDynamicY(String dynamicY){
getProperties().dynamicY = dynamicY;
getControlView().setY(getProperties().insertDynamicPos(dynamicY));
}
/**
* Generate a dynamic equation from an absolute position, used to scale properly across devices
* @param x The absolute position on the horizontal axis
* @return The equation as a String
*/
default String generateDynamicX(float x){
if(x + (getProperties().getWidth()/2f) > CallbackBridge.physicalWidth/2f){
return (x + getProperties().getWidth()) / CallbackBridge.physicalWidth + " * ${screen_width} - ${width}";
}else{
return x / CallbackBridge.physicalWidth + " * ${screen_width}";
}
}
/**
* Generate a dynamic equation from an absolute position, used to scale properly across devices
* @param y The absolute position on the vertical axis
* @return The equation as a String
*/
default String generateDynamicY(float y){
if(y + (getProperties().getHeight()/2f) > CallbackBridge.physicalHeight/2f){
return (y + getProperties().getHeight()) / CallbackBridge.physicalHeight + " * ${screen_height} - ${height}";
}else{
return y / CallbackBridge.physicalHeight + " * ${screen_height}";
}
}
/** Regenerate and apply coordinates with supposedly modified properties */
default void regenerateDynamicCoordinates(){
getProperties().dynamicX = generateDynamicX(getControlView().getX());
getProperties().dynamicY = generateDynamicY(getControlView().getY());
updateProperties();
}
/**
* Do a pre-conversion of an equation using values from a button,
* so the variables can be used for another button
*
* Internal use only.
* @param equation The dynamic position as a String
* @param button The button to get the values from.
* @return The pre-processed equation as a String.
*/
default String applySize(String equation, ControlInterface button){
return equation
.replace("${right}", "(${screen_width} - ${width})")
.replace("${bottom}","(${screen_height} - ${height})")
.replace("${height}", "(px(" + Tools.pxToDp(button.getProperties().getHeight()) + ") /" + PREF_BUTTONSIZE + " * ${preferred_scale})")
.replace("${width}", "(px(" + Tools.pxToDp(button.getProperties().getWidth()) + ") / " + PREF_BUTTONSIZE + " * ${preferred_scale})");
}
/** Convert a size percentage into a px size */
default int computeStrokeWidth(float widthInPercent){
float maxSize = Math.max(getProperties().getWidth(), getProperties().getHeight());
return (int)((maxSize/2) * (widthInPercent/100));
}
/** Convert a corner radius percentage into a px corner radius */
default float computeCornerRadius(float radiusInPercent){
float minSize = Math.min(getProperties().getWidth(), getProperties().getHeight());
return (minSize/2) * (radiusInPercent/100);
}
/**
* Passe a series of checks to determine if the ControlButton is available to be snapped on.
*
* @param button The button to check
* @return whether or not the button
*/
default boolean canSnap(ControlInterface button){
float MIN_DISTANCE = Tools.dpToPx(8);
if(button == this) return false;
if(net.kdt.pojavlaunch.utils.MathUtils.dist(
button.getControlView().getX() + button.getControlView().getWidth()/2f,
button.getControlView().getY() + button.getControlView().getHeight()/2f,
getControlView().getX() + getControlView().getWidth()/2f,
getControlView().getY() + getControlView().getHeight()/2f)
> Math.max(button.getControlView().getWidth()/2f + getControlView().getWidth()/2f,
button.getControlView().getHeight()/2f + getControlView().getHeight()/2f) + MIN_DISTANCE) return false;
return true;
}
/**
* Try to snap, then align to neighboring buttons, given the provided coordinates.
* The new position is automatically applied to the View,
* regardless of if the View snapped or not.
*
* The new position is always dynamic, thus replacing previous dynamic positions
*
* @param x Coordinate on the x axis
* @param y Coordinate on the y axis
*/
default void snapAndAlign(float x, float y){
float MIN_DISTANCE = Tools.dpToPx(8);
String dynamicX = generateDynamicX(x);
String dynamicY = generateDynamicY(y);
getControlView().setX(x);
getControlView().setY(y);
for(ControlInterface button : ((ControlLayout) getControlView().getParent()).getButtonChildren()){
//Step 1: Filter unwanted buttons
if(!canSnap(button)) continue;
//Step 2: Get Coordinates
float button_top = button.getControlView().getY();
float button_bottom = button_top + button.getControlView().getHeight();
float button_left = button.getControlView().getX();
float button_right = button_left + button.getControlView().getWidth();
float top = getControlView().getY();
float bottom = getControlView().getY() + getControlView().getHeight();
float left = getControlView().getX();
float right = getControlView().getX() + getControlView().getWidth();
//Step 3: For each axis, we try to snap to the nearest
if(Math.abs(top - button_bottom) < MIN_DISTANCE){ // Bottom snap
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " + ${margin}" ;
}else if(Math.abs(button_top - bottom) < MIN_DISTANCE){ //Top snap
dynamicY = applySize(button.getProperties().dynamicY, button) + " - ${height} - ${margin}";
}
if(!dynamicY.equals(generateDynamicY(getControlView().getY()))){ //If we snapped
if(Math.abs(button_left - left) < MIN_DISTANCE){ //Left align snap
dynamicX = applySize(button.getProperties().dynamicX, button);
}else if(Math.abs(button_right - right) < MIN_DISTANCE){ //Right align snap
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " - ${width}";
}
}
if(Math.abs(button_left - right) < MIN_DISTANCE){ //Left snap
dynamicX = applySize(button.getProperties().dynamicX, button) + " - ${width} - ${margin}";
}else if(Math.abs(left - button_right) < MIN_DISTANCE){ //Right snap
dynamicX = applySize(button.getProperties().dynamicX, button) + applySize(" + ${width}", button) + " + ${margin}";
}
if(!dynamicX.equals(generateDynamicX(getControlView().getX()))){ //If we snapped
if(Math.abs(button_top - top) < MIN_DISTANCE){ //Top align snap
dynamicY = applySize(button.getProperties().dynamicY, button);
}else if(Math.abs(button_bottom - bottom) < MIN_DISTANCE){ //Bottom align snap
dynamicY = applySize(button.getProperties().dynamicY, button) + applySize(" + ${height}", button) + " - ${height}";
}
}
}
setDynamicX(dynamicX);
setDynamicY(dynamicY);
}
/** Inject a touch listener on the view to make editing controls straight forward */
default void injectTouchEventBehavior(){
getControlView().setOnTouchListener(new View.OnTouchListener() {
private boolean mIsPointerOutOfBounds = false;
private boolean mCanTriggerLongClick = true;
private float downX, downY;
@Override
public boolean onTouch(View view, MotionEvent event) {
if(!getControlLayoutParent().getModifiable()){
// Basically, editing behavior is forced while in game behavior is specific
view.onTouchEvent(event);
return true;
}
/* If the button can be modified/moved */
//Instantiate the gesture detector only when needed
if (event.getActionMasked() == MotionEvent.ACTION_UP && mCanTriggerLongClick) {
//TODO change this.
onLongClick(view);
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mCanTriggerLongClick = true;
downX = event.getRawX() - view.getX();
downY = event.getRawY() - view.getY();
break;
case MotionEvent.ACTION_MOVE:
mCanTriggerLongClick = false;
getControlLayoutParent().adaptPanelPosition();
if (!getProperties().isDynamicBtn) {
snapAndAlign(
MathUtils.clamp(event.getRawX() - downX, 0, CallbackBridge.physicalWidth - view.getWidth()),
MathUtils.clamp(event.getRawY() - downY, 0, CallbackBridge.physicalHeight - view.getHeight())
);
}
break;
}
return true;
}
});
}
default void injectLayoutParamBehavior(){
getControlView().addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
getProperties().setWidth(right-left);
getProperties().setHeight(bottom-top);
setBackground();
// Re-calculate position
if(!getProperties().isDynamicBtn){
getControlView().setX(getControlView().getX());
getControlView().setY(getControlView().getY());
}else {
getControlView().setX(getProperties().insertDynamicPos(getProperties().dynamicX));
getControlView().setY(getProperties().insertDynamicPos(getProperties().dynamicY));
}
});
}
@Override
default boolean onLongClick(View v){
if (getControlLayoutParent().getModifiable()) {
getControlLayoutParent().editControlButton(this);
getControlLayoutParent().actionRow.setFollowedButton(this);
}
return true;
}
}

View file

@ -1,7 +1,6 @@
package net.kdt.pojavlaunch.customcontrols.buttons;
import android.os.Handler;
import android.os.Looper;
import android.annotation.SuppressLint;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
@ -12,6 +11,7 @@ import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
@SuppressLint("ViewConstructor")
public class ControlSubButton extends ControlButton {
public ControlDrawer parentDrawer;
@ -19,9 +19,6 @@ public class ControlSubButton extends ControlButton {
public ControlSubButton(ControlLayout layout, ControlData properties, ControlDrawer parentDrawer) {
super(layout, properties);
this.parentDrawer = parentDrawer;
filterProperties();
}
@ -49,16 +46,40 @@ public class ControlSubButton extends ControlButton {
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!mModifiable || parentDrawer.drawerData.orientation == ControlDrawerData.Orientation.FREE){
if(!getControlLayoutParent().getModifiable() || parentDrawer.drawerData.orientation == ControlDrawerData.Orientation.FREE){
return super.onTouchEvent(event);
}
if(mGestureDetector == null) mGestureDetector = new GestureDetector(getContext(), new SingleTapConfirm());
if (mGestureDetector.onTouchEvent(event)) {
mCanTriggerLongClick = true;
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
//mCanTriggerLongClick = true;
onLongClick(this);
}
return true;
}
@Override
public void cloneButton() {
ControlData cloneData = new ControlData(getProperties());
cloneData.dynamicX = "0.5 * ${screen_width}";
cloneData.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) getParent()).addSubButton(parentDrawer, cloneData);
}
@Override
public void removeButton() {
parentDrawer.drawerData.buttonProperties.remove(getProperties());
parentDrawer.drawerData.buttonProperties.remove(getProperties());
parentDrawer.buttons.remove(this);
parentDrawer.syncButtons();
super.removeButton();
}
@Override
public void snapAndAlign(float x, float y) {
if(parentDrawer.drawerData.orientation == ControlDrawerData.Orientation.FREE)
super.snapAndAlign(x, y);
// Else the button is forced into place
}
}

View file

@ -0,0 +1,24 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.view.View;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
/** Interface defining the behavior of action buttons */
public interface ActionButtonInterface extends View.OnClickListener {
/** HAS TO BE CALLED BY THE CONSTRUCTOR */
void init();
/** Called when the button should be made aware of the current target */
void setFollowedView(ControlInterface view);
/** Called when the button action should be executed on the target */
void onClick();
/** Whether the button should be shown, given the current contextual information that it has */
boolean shouldBeVisible();
@Override // Wrapper to remove the arg
default void onClick(View v){onClick();}
}

View file

@ -1,182 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.content.*;
import android.graphics.drawable.ColorDrawable;
import android.view.*;
import android.view.ViewGroup.*;
import android.widget.*;
import net.kdt.pojavlaunch.*;
import android.view.View.OnClickListener;
import net.kdt.pojavlaunch.customcontrols.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
import androidx.appcompat.app.*;
public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
private TextView mEditTextView;
private TextView mDeleteTextView;
private TextView mCloneTextView;
private final ControlButton mEditedButton;
public ActionPopupWindow(HandleView handleView, ControlButton button){
super(handleView);
this.mEditedButton = button;
}
@Override
protected void createPopupWindow() {
mPopupWindow = new PopupWindow(mHandleView.getContext(), null, android.R.attr.textSelectHandleWindowStyle);
mPopupWindow.setClippingEnabled(true);
}
@Override
protected void initContentView() {
LinearLayout linearLayout = new LinearLayout(mHandleView.getContext());
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
mContentView = linearLayout;
mContentView.setBackgroundResource(R.drawable.control_side_action_window);
LayoutInflater inflater = (LayoutInflater) mHandleView.getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LayoutParams wrapContent = new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mEditTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
mEditTextView.setLayoutParams(wrapContent);
mContentView.addView(mEditTextView);
mEditTextView.setText(R.string.global_edit);
mEditTextView.setOnClickListener(this);
mDeleteTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
mDeleteTextView.setLayoutParams(wrapContent);
mContentView.addView(mDeleteTextView);
mDeleteTextView.setText(R.string.global_remove);
mDeleteTextView.setOnClickListener(this);
mCloneTextView = (TextView) inflater.inflate(R.layout.control_action_popup_text, null);
mCloneTextView.setLayoutParams(wrapContent);
mContentView.addView(mCloneTextView);
mCloneTextView.setText(R.string.global_clone);
mCloneTextView.setOnClickListener(this);
}
@Override
public void show() {
super.show();
}
@Override
public void onClick(final View view) {
if (view == mEditTextView) {
if(mEditedButton instanceof ControlSubButton){
new EditControlSubButtonPopup((ControlSubButton) mEditedButton);
return;
}
if(mEditedButton instanceof ControlDrawer){
new EditControlDrawerPopup((ControlDrawer) mEditedButton);
return;
}
if(mEditedButton instanceof ControlButton){
new EditControlButtonPopup((ControlButton) mEditedButton);
return;
}
} else if (view == mDeleteTextView) {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(view.getContext());
alertBuilder.setCancelable(false);
alertBuilder.setMessage(view.getContext().getString(R.string.customctrl_remove, mHandleView.mView.getText()) + "?");
alertBuilder.setPositiveButton(R.string.global_remove, (p1, p2) -> {
ControlLayout layout = ((ControlLayout) mHandleView.mView.getParent());
if(mEditedButton instanceof ControlSubButton){
layout.removeControlSubButton((ControlSubButton) mEditedButton);
return;
}
if(mEditedButton instanceof ControlDrawer){
layout.removeControlDrawer((ControlDrawer) mEditedButton);
return;
}
if(mEditedButton instanceof ControlButton){
layout.removeControlButton((ControlButton) mEditedButton);
}
layout.removeControlButton(mHandleView.mView);
});
alertBuilder.setNegativeButton(android.R.string.cancel, null);
alertBuilder.show();
}else if(view == mCloneTextView) {
if(mEditedButton instanceof ControlDrawer){
ControlDrawerData cloneData = new ControlDrawerData(((ControlDrawer) mEditedButton).getDrawerData());
cloneData.properties.dynamicX = "0.5 * ${screen_width}";
cloneData.properties.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addDrawer(cloneData);
}else if(mEditedButton instanceof ControlSubButton){
ControlData cloneData = new ControlData(mEditedButton.getProperties());
cloneData.dynamicX = "0.5 * ${screen_width}";
cloneData.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addSubButton(((ControlSubButton) mEditedButton).parentDrawer, cloneData);
}else{
ControlData cloneData = new ControlData(mEditedButton.getProperties());
cloneData.dynamicX = "0.5 * ${screen_width}";
cloneData.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addControlButton(cloneData);
}
}
hide();
}
@Override
protected int getTextOffset() {
return 0;
}
@Override
protected int getVerticalLocalPosition(int line) {
return 0;
}
@Override
protected int clipVertically(int positionY) {
return positionY;
}
public static void setPercentageText(TextView textView, int progress){
textView.setText(progress + " %");
}
}

View file

@ -0,0 +1,146 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import androidx.core.math.MathUtils;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
/**
* Layout floating around a Control Button, displaying contextual actions
*/
public class ActionRow extends LinearLayout {
public static int SIDE_LEFT = 0x0;
public static int SIDE_TOP = 0x1;
public static int SIDE_RIGHT = 0x2;
public static int SIDE_BOTTOM = 0x3;
public static int SIDE_AUTO = 0x4;
public ActionRow(Context context) {
super(context); init();
}
public ActionRow(Context context, @Nullable AttributeSet attrs) {
super(context, attrs); init();
}
public final ViewTreeObserver.OnPreDrawListener mFollowedViewListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if(mFollowedView == null || !mFollowedView.isShown()){
hide();
return true;
}
setNewPosition();
return true;
}
};
private final ActionButtonInterface[] actionButtons = new ActionButtonInterface[3];
private View mFollowedView = null;
private int mSide = SIDE_TOP;
/** Add action buttons and configure them */
private void init(){
setVisibility(GONE);
setOrientation(HORIZONTAL);
setLayoutParams(new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
getResources().getDimensionPixelOffset(R.dimen._40sdp)
));
actionButtons[0] = new DeleteButton(getContext());
actionButtons[1] = new CloneButton(getContext());
actionButtons[2] = new AddSubButton(getContext());
// This is not pretty code, don't do this.
for(ActionButtonInterface buttonInterface: actionButtons){
View button = ((View)(buttonInterface));
addView(button, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1F));
}
setElevation(5F);
}
public void setFollowedButton(ControlInterface controlInterface){
if(mFollowedView != null)
mFollowedView.getViewTreeObserver().removeOnPreDrawListener(mFollowedViewListener);
for(ActionButtonInterface buttonInterface: actionButtons){
buttonInterface.setFollowedView(controlInterface);
((View)(buttonInterface)).setVisibility(buttonInterface.shouldBeVisible() ? VISIBLE : GONE);
}
setVisibility(VISIBLE);
mFollowedView = (View) controlInterface;
if(mFollowedView != null)
mFollowedView.getViewTreeObserver().addOnPreDrawListener(mFollowedViewListener);
}
private float getXPosition(int side){
if(side == SIDE_LEFT){
return mFollowedView.getX() - getWidth();
}else if(side == SIDE_RIGHT){
return mFollowedView.getX() + mFollowedView.getWidth();
}else{
return mFollowedView.getX() + mFollowedView.getWidth()/2f - getWidth()/2f;
}
}
private float getYPosition(int side){
if(side == SIDE_TOP){
return mFollowedView.getY() - getHeight();
} else if(side == SIDE_BOTTOM){
return mFollowedView.getY() + mFollowedView.getHeight();
}else{
return mFollowedView.getY() + mFollowedView.getHeight()/2f - getHeight()/2f;
}
}
private void setNewPosition(){
if(mFollowedView == null) return;
int side = pickSide();
setX(MathUtils.clamp(getXPosition(side), 0, currentDisplayMetrics.widthPixels - getWidth()));
setY(getYPosition(side));
}
private int pickSide(){
if(mFollowedView == null) return mSide; //Value should not matter
if(mSide != SIDE_AUTO) return mSide;
//TODO improve the "algo"
ViewGroup parent = ((ViewGroup) mFollowedView.getParent());
if(parent == null) return mSide;//Value should not matter
int side = mFollowedView.getX() + getWidth()/2f > parent.getWidth()/2f
? SIDE_LEFT
: SIDE_RIGHT;
float futurePos = getYPosition(side);
if(futurePos + getHeight() > (parent.getHeight() + getHeight()/2)){
side = SIDE_TOP;
}else if (futurePos < -getHeight()/2){
side = SIDE_BOTTOM;
}
return side;
}
public void hide(){
if(mFollowedView != null)
mFollowedView.getViewTreeObserver().removeOnPreDrawListener(mFollowedViewListener);
setVisibility(GONE);
}
}

View file

@ -0,0 +1,51 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
@SuppressLint("AppCompatCustomView")
public class AddSubButton extends Button implements ActionButtonInterface {
public AddSubButton(Context context) {super(context); init();}
public AddSubButton(Context context, @Nullable AttributeSet attrs) {super(context, attrs); init();}
public void init() {
setText("Add Button");
setOnClickListener(this);
}
private ControlInterface mCurrentlySelectedButton = null;
@Override
public boolean shouldBeVisible() {
return mCurrentlySelectedButton != null && mCurrentlySelectedButton instanceof ControlDrawer;
}
@Override
public void setFollowedView(ControlInterface view) {
mCurrentlySelectedButton = view;
}
@Override
public void onClick() {
if(mCurrentlySelectedButton instanceof ControlDrawer){
((ControlDrawer)mCurrentlySelectedButton).getControlLayoutParent().addSubButton(
(ControlDrawer)mCurrentlySelectedButton,
new ControlData()
);
}
}
}

View file

@ -0,0 +1,47 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
@SuppressLint("AppCompatCustomView")
public class CloneButton extends Button implements ActionButtonInterface {
public CloneButton(Context context) {super(context); init();}
public CloneButton(Context context, @Nullable AttributeSet attrs) {super(context, attrs); init();}
public void init() {
setOnClickListener(this);
setText("CLONE");
}
private ControlInterface mCurrentlySelectedButton = null;
@Override
public boolean shouldBeVisible() {
return mCurrentlySelectedButton != null;
}
@Override
public void setFollowedView(ControlInterface view) {
mCurrentlySelectedButton = view;
}
@Override
public void onClick() {
if(mCurrentlySelectedButton == null) return;
mCurrentlySelectedButton.cloneButton();
mCurrentlySelectedButton.getControlLayoutParent().removeEditWindow();
}
}

View file

@ -0,0 +1,98 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
public class ControlHandleView extends View {
public ControlHandleView(Context context) {
super(context);
init();
}
public ControlHandleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private final Drawable mDrawable = ResourcesCompat.getDrawable(getResources(), R.drawable.ic_view_handle, getContext().getTheme());
private ControlInterface mView;
private float mXOffset, mYOffset;
private final ViewTreeObserver.OnPreDrawListener mPositionListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if(mView == null || !mView.getControlView().isShown()){
hide();
return true;
}
setX(mView.getControlView().getX() + mView.getControlView().getWidth());
setY(mView.getControlView().getY() + mView.getControlView().getHeight());
return true;
}
};
private void init(){
int size = getResources().getDimensionPixelOffset(R.dimen._22sdp);
mDrawable.setBounds(0,0,size,size);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(size, size);
setLayoutParams(params);
setBackground(mDrawable);
setElevation(3);
}
public void setControlButton(ControlInterface controlInterface){
if(mView != null) mView.getControlView().getViewTreeObserver().removeOnPreDrawListener(mPositionListener);
setVisibility(VISIBLE);
mView = controlInterface;
mView.getControlView().getViewTreeObserver().addOnPreDrawListener(mPositionListener);
setX(controlInterface.getControlView().getX() + controlInterface.getControlView().getWidth());
setY(controlInterface.getControlView().getY() + controlInterface.getControlView().getHeight());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
mXOffset = event.getX();
mYOffset = event.getY();
break;
case MotionEvent.ACTION_MOVE:
setX(getX() + event.getX() - mXOffset);
setY(getY() + event.getY() - mYOffset);
System.out.println(getX() - mView.getControlView().getX());
System.out.println(getY() - mView.getControlView().getY());
mView.getProperties().setWidth(getX() - mView.getControlView().getX());
mView.getProperties().setHeight(getY() - mView.getControlView().getY());
mView.regenerateDynamicCoordinates();
break;
}
return true;
}
public void hide(){
if(mView != null)
mView.getControlView().getViewTreeObserver().removeOnPreDrawListener(mPositionListener);
setVisibility(GONE);
}
}

View file

@ -0,0 +1,46 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
@SuppressLint("AppCompatCustomView")
public class DeleteButton extends Button implements ActionButtonInterface {
public DeleteButton(Context context) {super(context); init();}
public DeleteButton(Context context, @Nullable AttributeSet attrs) {super(context, attrs); init();}
public void init() {
setOnClickListener(this);
setText("DELETE");
}
private ControlInterface mCurrentlySelectedButton = null;
@Override
public boolean shouldBeVisible() {
return mCurrentlySelectedButton != null;
}
@Override
public void setFollowedView(ControlInterface view) {
mCurrentlySelectedButton = view;
}
@Override
public void onClick() {
if(mCurrentlySelectedButton == null) return;
mCurrentlySelectedButton.removeButton();
}
}

View file

@ -0,0 +1,40 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.VectorDrawable;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import net.kdt.pojavlaunch.R;
public class DrawerPullButton extends View {
public DrawerPullButton(Context context) {super(context); init();}
public DrawerPullButton(Context context, @Nullable AttributeSet attrs) {super(context, attrs); init();}
private final Paint mPaint = new Paint();
private VectorDrawableCompat mDrawable;
private void init(){
mDrawable = VectorDrawableCompat.create(getContext().getResources(), R.drawable.ic_sharp_settings_24, null);
setAlpha(0.33f);
}
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.BLACK);
canvas.drawArc(0,-getHeight(),getWidth(), getHeight(), 0, 180, true, mPaint);
mPaint.setColor(Color.WHITE);
mDrawable.setBounds(0, 0, canvas.getHeight(), canvas.getHeight());
canvas.save();
canvas.translate((canvas.getWidth()-canvas.getHeight())/2, 0);
mDrawable.draw(canvas);
canvas.restore();
}
}

View file

@ -1,340 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
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.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import top.defaults.checkerboarddrawable.CheckerboardDrawable;
import static net.kdt.pojavlaunch.customcontrols.handleview.ActionPopupWindow.setPercentageText;
public class EditControlButtonPopup {
protected AlertDialog mDialog;
protected View mRootView;
protected AlertDialog.Builder mBuilder;
protected EditText mNameEditText;
protected Spinner[] mKeycodeSpinners;
protected CheckBox mToggleCheckbox;
protected CheckBox mPassthroughCheckbox;
protected CheckBox mSwipeableCheckbox;
protected CheckBox mDynamicPositionCheckbox;
protected EditText mWidthEditText;
protected EditText mHeightEditText;
protected EditText mDynamicXEditText;
protected EditText mDynamicYEditText;
protected SeekBar mOpacitySeekbar;
protected SeekBar mCornerRadiusSeekbar;
protected SeekBar mStrokeWidthSeekbar;
protected ImageButton mBackgroundColorButton;
protected ImageButton mStrokeColorButton;
protected TextView mOpacityTextView;
protected TextView mCornerRadiusTextView;
protected TextView mStrokeWidthTextView;
protected TextView mStrokeColorTextView;
protected ColorSelector mColorSelector;
protected ImageView mEditingView;
protected final ControlButton mControlButton;
protected final ControlData mProperties;
protected ArrayAdapter<String> mAdapter;
protected String[] mSpecialArray;
public EditControlButtonPopup(ControlButton button){
this.mControlButton = button;
this.mProperties = button.getProperties();
initializeEditDialog(button.getContext());
//Create the finalized dialog
mDialog = mBuilder.create();
mDialog.setOnShowListener(dialogInterface -> setEditDialogValues());
mDialog.show();
}
protected void initializeEditDialog(Context ctx){
//Create the editing dialog
LayoutInflater layoutInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mRootView = layoutInflater.inflate(R.layout.dialog_control_button_setting,null);
mBuilder = new AlertDialog.Builder(ctx);
mBuilder.setTitle(ctx.getResources().getString(R.string.customctrl_edit, mProperties.name));
mBuilder.setView(mRootView);
//Linking a lot of stuff
mNameEditText = mRootView.findViewById(R.id.editName_editText);
mKeycodeSpinners = new Spinner[]{
mRootView.findViewById(R.id.editMapping_spinner_1),
mRootView.findViewById(R.id.editMapping_spinner_2),
mRootView.findViewById(R.id.editMapping_spinner_3),
mRootView.findViewById(R.id.editMapping_spinner_4)
};
mToggleCheckbox = mRootView.findViewById(R.id.checkboxToggle);
mPassthroughCheckbox = mRootView.findViewById(R.id.checkboxPassThrough);
mSwipeableCheckbox = mRootView.findViewById(R.id.checkboxSwipeable);
mWidthEditText = mRootView.findViewById(R.id.editSize_editTextX);
mHeightEditText = mRootView.findViewById(R.id.editSize_editTextY);
mDynamicXEditText = mRootView.findViewById(R.id.editDynamicPositionX_editText);
mDynamicYEditText = mRootView.findViewById(R.id.editDynamicPositionY_editText);
mOpacitySeekbar = mRootView.findViewById(R.id.editButtonOpacity_seekbar);
mCornerRadiusSeekbar = mRootView.findViewById(R.id.editCornerRadius_seekbar);
mStrokeWidthSeekbar = mRootView.findViewById(R.id.editStrokeWidth_seekbar);
SeekBar.OnSeekBarChangeListener changeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if(seekBar.equals(mCornerRadiusSeekbar)) {
setPercentageText(mCornerRadiusTextView, i);
return;
}
if(seekBar.equals(mOpacitySeekbar)) {
setPercentageText(mOpacityTextView, i);
return;
}
if(seekBar.equals(mStrokeWidthSeekbar)) {
setPercentageText(mStrokeWidthTextView, i);
mStrokeColorTextView.setVisibility(i == 0 ? View.GONE : View.VISIBLE);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {/*STUB*/}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {/*STUB*/}
};
//Add listeners, too bad I don't need all the methods
mOpacitySeekbar.setOnSeekBarChangeListener(changeListener);
mCornerRadiusSeekbar.setOnSeekBarChangeListener(changeListener);
mStrokeWidthSeekbar.setOnSeekBarChangeListener(changeListener);
mBackgroundColorButton = mRootView.findViewById(R.id.editBackgroundColor_imageButton);
mStrokeColorButton = mRootView.findViewById(R.id.editStrokeColor_imageButton);
mOpacityTextView = mRootView.findViewById(R.id.editButtonOpacity_textView_percent);
mCornerRadiusTextView = mRootView.findViewById(R.id.editCornerRadius_textView_percent);
mStrokeWidthTextView = mRootView.findViewById(R.id.editStrokeWidth_textView_percent);
mStrokeColorTextView = mRootView.findViewById(R.id.editStrokeColor_textView);
mDynamicPositionCheckbox = mRootView.findViewById(R.id.checkboxDynamicPosition);
mDynamicPositionCheckbox.setOnCheckedChangeListener((btn, checked) -> {
mDynamicXEditText.setEnabled(checked);
mDynamicYEditText.setEnabled(checked);
});
//Initialize adapter for keycodes
mAdapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
String[] oldSpecialArr = ControlData.buildSpecialButtonArray();
mSpecialArray = new String[oldSpecialArr.length];
for (int i = 0; i < mSpecialArray.length; i++) {
mSpecialArray[i] = "SPECIAL_" + oldSpecialArr[mSpecialArray.length - i - 1];
}
mAdapter.addAll(mSpecialArray);
mAdapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName());
mAdapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
for (Spinner spinner : mKeycodeSpinners) {
spinner.setAdapter(mAdapter);
}
mColorSelector = new ColorSelector(ctx,color -> mEditingView.setImageDrawable(new ColorDrawable(color)));
//Set color imageButton behavior
mBackgroundColorButton.setOnClickListener(view -> showColorEditor((ImageView) view));
mStrokeColorButton.setOnClickListener(view -> showColorEditor((ImageView) view));
//Set dialog buttons behavior
setupDialogButtons();
hideUselessViews();
defineDynamicCheckChange();
setupCheckerboards();
}
protected void showColorEditor(ImageView imgView) {
mEditingView = imgView;
mColorSelector.show(((ColorDrawable)(imgView.getDrawable())).getColor());
}
protected void setupDialogButtons(){
//Set dialog buttons behavior
mBuilder.setPositiveButton(android.R.string.ok, (dialogInterface1, i) -> {
if(!hasPropertiesErrors(mDialog.getContext())){
saveProperties();
}
});
mBuilder.setNegativeButton(android.R.string.cancel, null);
}
protected void hideUselessViews(){
(mRootView.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
mDynamicXEditText.setVisibility(View.GONE);
mDynamicYEditText.setVisibility(View.GONE);
//Hide the color choice if the width is 0.
mStrokeColorTextView.setVisibility(mProperties.strokeWidth == 0 ? View.GONE : View.VISIBLE);
}
protected void defineDynamicCheckChange(){
mDynamicPositionCheckbox.setOnCheckedChangeListener((compoundButton, b) -> {
int visibility = b ? View.VISIBLE : View.GONE;
(mRootView.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(visibility);
(mRootView.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(visibility);
mDynamicXEditText.setVisibility(visibility);
mDynamicYEditText.setVisibility(visibility);
});
}
private void setupCheckerboards(){
CheckerboardDrawable drawable = new CheckerboardDrawable.Builder()
.colorEven(Color.LTGRAY)
.colorOdd(Color.WHITE)
.size((int) Tools.dpToPx(20))
.build();
mBackgroundColorButton.setBackground(drawable);
mStrokeColorButton.setBackground(drawable);
}
protected void setEditDialogValues(){
mNameEditText.setText(mProperties.name);
mToggleCheckbox.setChecked(mProperties.isToggle);
mPassthroughCheckbox.setChecked(mProperties.passThruEnabled);
mSwipeableCheckbox.setChecked(mProperties.isSwipeable);
mWidthEditText.setText(Float.toString(mProperties.getWidth()));
mHeightEditText.setText(Float.toString(mProperties.getHeight()));
mDynamicXEditText.setEnabled(mProperties.isDynamicBtn);
mDynamicYEditText.setEnabled(mProperties.isDynamicBtn);
mDynamicXEditText.setText(mProperties.dynamicX);
mDynamicYEditText.setText(mProperties.dynamicY);
mOpacitySeekbar.setProgress((int) (mProperties.opacity*100));
mStrokeWidthSeekbar.setProgress(mProperties.strokeWidth);
mCornerRadiusSeekbar.setProgress((int) mProperties.cornerRadius);
mBackgroundColorButton.setImageDrawable(new ColorDrawable(mProperties.bgColor));
mStrokeColorButton.setImageDrawable(new ColorDrawable(mProperties.strokeColor));
setPercentageText(mCornerRadiusTextView, mCornerRadiusSeekbar.getProgress());
setPercentageText(mOpacityTextView, mOpacitySeekbar.getProgress());
setPercentageText(mStrokeWidthTextView, mStrokeWidthSeekbar.getProgress());
mDynamicPositionCheckbox.setChecked(mProperties.isDynamicBtn);
for(int i = 0; i< mProperties.keycodes.length; i++){
if (mProperties.keycodes[i] < 0) {
mKeycodeSpinners[i].setSelection(mProperties.keycodes[i] + mSpecialArray.length);
} else {
mKeycodeSpinners[i].setSelection(EfficientAndroidLWJGLKeycode.getIndexByValue(mProperties.keycodes[i]) + mSpecialArray.length);
}
}
}
protected boolean hasPropertiesErrors(Context ctx){
if (mNameEditText.getText().toString().isEmpty()) {
mNameEditText.setError(ctx.getResources().getString(R.string.global_error_field_empty));
return true;
}
if (mProperties.isDynamicBtn) {
int errorAt = 0;
try {
mProperties.insertDynamicPos(mDynamicXEditText.getText().toString());
errorAt = 1;
mProperties.insertDynamicPos(mDynamicYEditText.getText().toString());
} catch (Throwable th) {
(errorAt == 0 ? mDynamicXEditText : mDynamicYEditText).setError(th.getMessage());
return true;
}
}
return false;
}
protected void saveProperties(){
//This method assumes there are no error.
mProperties.name = mNameEditText.getText().toString();
//Keycodes
for(int i = 0; i< mKeycodeSpinners.length; ++i){
if (mKeycodeSpinners[i].getSelectedItemPosition() < mSpecialArray.length) {
mProperties.keycodes[i] = mKeycodeSpinners[i].getSelectedItemPosition() - mSpecialArray.length;
} else {
mProperties.keycodes[i] = EfficientAndroidLWJGLKeycode.getValueByIndex(mKeycodeSpinners[i].getSelectedItemPosition() - mSpecialArray.length);
}
}
mProperties.opacity = mOpacitySeekbar.getProgress()/100f;
mProperties.strokeWidth = mStrokeWidthSeekbar.getProgress();
mProperties.cornerRadius = mCornerRadiusSeekbar.getProgress();
mProperties.bgColor = ((ColorDrawable) mBackgroundColorButton.getDrawable()).getColor();
mProperties.strokeColor = ((ColorDrawable) mStrokeColorButton.getDrawable()).getColor();
mProperties.isToggle = mToggleCheckbox.isChecked();
mProperties.passThruEnabled = mPassthroughCheckbox.isChecked();
mProperties.isSwipeable = mSwipeableCheckbox.isChecked();
mProperties.setWidth(Float.parseFloat(mWidthEditText.getText().toString()));
mProperties.setHeight(Float.parseFloat(mHeightEditText.getText().toString()));
mProperties.isDynamicBtn = mDynamicPositionCheckbox.isChecked();
if(!mDynamicXEditText.getText().toString().isEmpty()) mProperties.dynamicX = mDynamicXEditText.getText().toString();
if(!mDynamicYEditText.getText().toString().isEmpty()) mProperties.dynamicY = mDynamicYEditText.getText().toString();
mControlButton.updateProperties();
}
}

View file

@ -1,90 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
public class EditControlDrawerPopup extends EditControlButtonPopup{
private Spinner mOrientationSpinner;
private final ControlDrawer mDrawer;
private final ControlDrawerData mDrawerData;
public EditControlDrawerPopup(ControlDrawer editedButton) {
super(editedButton);
mDrawer = editedButton;
mDrawerData = editedButton.getDrawerData();
}
@Override
protected void hideUselessViews() {
(mRootView.findViewById(R.id.editMapping_textView)).setVisibility(View.GONE);
mPassthroughCheckbox.setVisibility(View.GONE);
mToggleCheckbox.setVisibility(View.GONE);
mSwipeableCheckbox.setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
mDynamicXEditText.setVisibility(View.GONE);
mDynamicYEditText.setVisibility(View.GONE);
}
@Override
protected void initializeEditDialog(Context ctx) {
super.initializeEditDialog(ctx);
mOrientationSpinner = mRootView.findViewById(R.id.editOrientation_spinner);
ArrayAdapter<ControlDrawerData.Orientation> adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
adapter.addAll(ControlDrawerData.getOrientations());
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
mOrientationSpinner.setAdapter(adapter);
}
@Override
protected void setEditDialogValues() {
super.setEditDialogValues();
mOrientationSpinner.setSelection(ControlDrawerData.orientationToInt(mDrawerData.orientation));
//Using the dialog to replace the button behavior allows us not to dismiss the window
mDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> {
ControlLayout layout = (ControlLayout) mDrawer.getParent();
ControlData controlData = new ControlData(mDrawerData.properties);
controlData.name = "new";
layout.addSubButton(mDrawer, controlData);
Context ctx = mDialog.getContext();
Toast.makeText(ctx, ctx.getString(R.string.customctrl_add_subbutton_message,
mDrawer.getDrawerData().buttonProperties.size()), Toast.LENGTH_SHORT).show();
});
}
@Override
protected void setupDialogButtons() {
super.setupDialogButtons();
mBuilder.setNeutralButton(mRootView.getResources().getString(R.string.customctrl_addsubbutton), null);
}
@Override
protected void saveProperties() {
mDrawerData.orientation = ControlDrawerData.intToOrientation(mOrientationSpinner.getSelectedItemPosition());
super.saveProperties();
}
}

View file

@ -0,0 +1,540 @@
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.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.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
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.ColorSelectionListener;
import net.kdt.pojavlaunch.colorselector.ColorSelector;
import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlDrawer;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlInterface;
import java.util.Arrays;
import java.util.Collections;
/**
* Class providing a sort of popup on top of a Layout, allowing to edit a given ControlButton
*/
public class EditControlPopup {
private final DefocusableScrollView mScrollView;
private ConstraintLayout mRootView;
private final ColorSelector mColorSelector;
private final ObjectAnimator mEditPopupAnimator;
private final ObjectAnimator mColorEditorAnimator;
private boolean mDisplaying = false;
private boolean mDisplayingColor = false;
public boolean internalChanges = false; // True when we programmatically change stuff.
private ControlInterface mCurrentlyEditedButton;
private final int mMargin;
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;
if(right != (int)safeParseFloat(mWidthEditText.getText().toString())){
mWidthEditText.setText(String.valueOf(right - left));
}
if(bottom != (int) safeParseFloat(mHeightEditText.getText().toString())){
mHeightEditText.setText(String.valueOf(bottom - top));
}
internalChanges = false;
}
};
protected EditText mNameEditText, mWidthEditText, mHeightEditText;
@SuppressLint("UseSwitchCompatOrMaterialCode")
protected Switch mToggleSwitch, mPassthroughSwitch, mSwipeableSwitch;
protected Spinner mOrientationSpinner;
protected Spinner[] mKeycodeSpinners = new Spinner[4];
protected SeekBar mStrokeWidthSeekbar, mCornerRadiusSeekbar, mAlphaSeekbar;
protected TextView mStrokePercentTextView, mCornerRadiusPercentTextView, mAlphaPercentTextView;
protected TextView mSelectBackgroundColor, mSelectStrokeColor;
protected ArrayAdapter<String> mAdapter;
protected String[] mSpecialArray;
// Decorative textviews
private TextView mOrientationTextView, mMappingTextView, mNameTextView, mCornerRadiusTextView;
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().setX(-context.getResources().getDimensionPixelOffset(R.dimen._230sdp));
mEditPopupAnimator = ObjectAnimator.ofFloat(mScrollView, "x", 0).setDuration(1000);
mColorEditorAnimator = ObjectAnimator.ofFloat(mColorSelector.getRootView(), "x", 0).setDuration(1000);
Interpolator decelerate = new AccelerateDecelerateInterpolator();
mEditPopupAnimator.setInterpolator(decelerate);
mColorEditorAnimator.setInterpolator(decelerate);
mScrollView.setElevation(10);
mScrollView.setX(-context.getResources().getDimensionPixelOffset(R.dimen._230sdp));
bindLayout();
loadAdapter();
setupRealTimeListeners();
}
/** 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();
}
}
mDisplayingColor = true;
if(color != -1)
mColorSelector.show(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();
for (int i = 0; i < mSpecialArray.length; i++) {
//TODO this will break for sure
mSpecialArray[i] = "SPECIAL_" + mSpecialArray[i];
}
Collections.reverse(Arrays.asList(mSpecialArray));
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);
}
}
private boolean isAtRight(){
return mScrollView.getX() > currentDisplayMetrics.widthPixels/2f;
}
public static void setPercentageText(TextView textView, int progress){
textView.setText(progress + " %");
}
/* LOADING VALUES */
/** Load values for basic control data */
public void loadValues(ControlData data){
setDefaultVisibilitySetting();
mOrientationTextView.setVisibility(GONE);
mOrientationSpinner.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(data.strokeWidth);
mCornerRadiusSeekbar.setProgress((int) data.cornerRadius);
setPercentageText(mAlphaPercentTextView, (int) (data.opacity*100));
setPercentageText(mStrokePercentTextView, data.strokeWidth);
setPercentageText(mCornerRadiusPercentTextView, (int) data.cornerRadius);
mToggleSwitch.setChecked(data.isToggle);
mPassthroughSwitch.setChecked(data.passThruEnabled);
mSwipeableSwitch.setChecked(data.isSwipeable);
for(int i = 0; i< data.keycodes.length; i++){
if (data.keycodes[i] < 0) {
mKeycodeSpinners[i].setSelection(data.keycodes[i] + mSpecialArray.length);
} else {
mKeycodeSpinners[i].setSelection(EfficientAndroidLWJGLKeycode.getIndexByValue(data.keycodes[i]) + mSpecialArray.length);
}
}
}
/** Load values for extended control data */
public void loadValues(ControlDrawerData data){
loadValues(data.properties);
mOrientationSpinner.setSelection(
ControlDrawerData.orientationToInt(data.orientation));
mMappingTextView.setVisibility(GONE);
mKeycodeSpinners[0].setVisibility(GONE);
mKeycodeSpinners[1].setVisibility(GONE);
mKeycodeSpinners[2].setVisibility(GONE);
mKeycodeSpinners[3].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(ControlData data){
loadValues(data);
mMappingTextView.setVisibility(GONE);
mKeycodeSpinners[0].setVisibility(GONE);
mKeycodeSpinners[1].setVisibility(GONE);
mKeycodeSpinners[2].setVisibility(GONE);
mKeycodeSpinners[3].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);
}
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);
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);
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);
//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);
}
/**
* 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;
mCurrentlyEditedButton.getProperties().setWidth(safeParseFloat(s.toString()));
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;
mCurrentlyEditedButton.getProperties().setHeight(safeParseFloat(s.toString()));
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;
});
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();
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;
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.length) {
mCurrentlyEditedButton.getProperties().keycodes[finalI] = mKeycodeSpinners[finalI].getSelectedItemPosition() - mSpecialArray.length;
} else {
mCurrentlyEditedButton.getProperties().keycodes[finalI] = EfficientAndroidLWJGLKeycode.getValueByIndex(mKeycodeSpinners[finalI].getSelectedItemPosition() - mSpecialArray.length);
}
}
@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) {}
});
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 = 10; // 10 px as a fallback
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);
}
}

View file

@ -1,28 +0,0 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.view.View;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
public class EditControlSubButtonPopup extends EditControlButtonPopup{
public EditControlSubButtonPopup(ControlButton button){
super(button);
}
@Override
protected void hideUselessViews() {
(mRootView.findViewById(R.id.editSize_textView)).setVisibility(View.GONE);
(mRootView.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
mDynamicPositionCheckbox.setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
mDynamicXEditText.setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
mDynamicYEditText.setVisibility(View.GONE);
}
}

View file

@ -1,405 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.graphics.*;
import android.graphics.drawable.*;
import android.os.*;
import android.view.*;
import android.widget.*;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
public abstract class HandleView extends View implements ViewPositionListener, View.OnLongClickListener {
protected Drawable mDrawable;
protected Drawable mDrawableRtl;
private final PopupWindow mContainer;
// Position with respect to the parent TextView
private int mPositionX, mPositionY;
private boolean mIsDragging;
// Offset from touch position to mPosition
private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
protected int mHotspotX;
protected int mHorizontalGravity;
// Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
private float mTouchOffsetY;
// Where the touch position should be on the handle to ensure a maximum cursor visibility
private float mIdealVerticalOffset;
// Parent's (TextView) previous position in window
private int mLastParentX, mLastParentY;
// Transient action popup window for Paste and Replace actions
protected ActionPopupWindow mActionPopupWindow;
// Previous text character offset
private int mPreviousOffset = -1;
// Previous text character offset
private boolean mPositionHasChanged = true;
// Used to delay the appearance of the action popup window
private Runnable mActionPopupShower;
// Minimum touch target size for handles
private int mMinSize;
protected ControlButton mView;
// MOD: Addition. Save old size of parent
private int mDownWidth, mDownHeight;
// int mWindowPosX, mWindowPosY;
private PositionListener mPositionListener;
// Touch-up filter: number of previous positions remembered
private static final int HISTORY_SIZE = 5;
private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
private int mPreviousOffsetIndex = 0;
private int mNumberPreviousOffsets = 0;
// Addition
private float mDownX, mDownY;
public HandleView(ControlButton view) {
super(view.getContext());
mView = view;
mDownWidth = view.getLayoutParams().width;
mDownHeight = view.getLayoutParams().height;
mContainer = new PopupWindow(view.getContext(), null, android.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
mContainer.setClippingEnabled(false);
mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
mContainer.setContentView(this);
mDrawableRtl = view.getContext().getDrawable(R.drawable.ic_view_handle);
mMinSize = view.getContext().getResources().getDimensionPixelSize(R.dimen.text_handle_min_size);
setOnLongClickListener(this);
updateDrawable();
final int handleHeight = getPreferredHeight();
mTouchOffsetY = -0.3f * handleHeight;
mIdealVerticalOffset = 0.7f * handleHeight;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getPreferredWidth(), getPreferredHeight());
}
@Override
protected void onDraw(Canvas c) {
final int drawWidth = mDrawable.getIntrinsicWidth();
final int left = getHorizontalOffset();
mDrawable.setBounds(left, 0, left + drawWidth, mDrawable.getIntrinsicHeight());
mDrawable.draw(c);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
ViewGroup.LayoutParams params = mView.getLayoutParams();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
startTouchUpFilter(getCurrentCursorOffset());
mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
final PositionListener positionListener = getPositionListener();
mLastParentX = positionListener.getPositionX();
mLastParentY = positionListener.getPositionY();
mIsDragging = true;
// MOD: Addition
mDownX = ev.getRawX();
mDownY = ev.getRawY();
mDownWidth = params.width;
mDownHeight = params.height;
break;
}
case MotionEvent.ACTION_MOVE: {
final float rawX = ev.getRawX();
final float rawY = ev.getRawY();
// Vertical hysteresis: vertical down movement tends to snap to ideal offset
final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
float newVerticalOffset;
if (previousVerticalOffset < mIdealVerticalOffset) {
newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
} else {
newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
}
mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
int newWidth = (int) (mDownWidth + (rawX - mDownX));
int newHeight = (int) (mDownHeight + (rawY - mDownY));
// mDownX = rawX;
// mDownY = rawY;
params.width = Math.max(50, newWidth);
params.height = Math.max(50, newHeight);
mView.setLayoutParams(params);
updatePosition(newPosX, newPosY);
// break;
return true;
}
case MotionEvent.ACTION_UP:
filterOnTouchUp();
mIsDragging = false;
break;
case MotionEvent.ACTION_CANCEL:
mIsDragging = false;
break;
}
return true;
}
public PositionListener getPositionListener() {
if (mPositionListener == null) {
mPositionListener = new PositionListener(mView);
}
return mPositionListener;
}
protected void updateDrawable() {
// final int offset = getCurrentCursorOffset();
final boolean isRtlCharAtOffset = true; // mView.getLayout().isRtlCharAt(offset);
mDrawable = mDrawableRtl; //isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
mHorizontalGravity = getHorizontalGravity(isRtlCharAtOffset);
}
protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
protected abstract int getHorizontalGravity(boolean isRtlRun);
private void startTouchUpFilter(int offset) {
mNumberPreviousOffsets = 0;
addPositionToTouchUpFilter(offset);
}
private void addPositionToTouchUpFilter(int offset) {
mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
mPreviousOffsets[mPreviousOffsetIndex] = offset;
mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
mNumberPreviousOffsets++;
}
private void filterOnTouchUp() {
final long now = SystemClock.uptimeMillis();
int i = 0;
int index = mPreviousOffsetIndex;
final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
i++;
index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
}
if (i > 0 && i < iMax &&
(now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
positionAtCursorOffset(mPreviousOffsets[index], false);
}
}
public boolean offsetHasBeenChanged() {
return mNumberPreviousOffsets > 1;
}
private int getPreferredWidth() {
return Math.max(mDrawable.getIntrinsicWidth(), mMinSize);
}
private int getPreferredHeight() {
return Math.max(mDrawable.getIntrinsicHeight(), mMinSize);
}
public void show() {
if (isShowing()) return;
getPositionListener().addSubscriber(this, true /* local position may change */);
// Make sure the offset is always considered new, even when focusing at same position
mPreviousOffset = -1;
positionAtCursorOffset(getCurrentCursorOffset(), false);
hideActionPopupWindow();
}
protected void dismiss() {
mIsDragging = false;
mContainer.dismiss();
onDetached();
}
public void hide() {
dismiss();
getPositionListener().removeSubscriber(this);
}
void showActionPopupWindow(int delay, ControlButton button) {
if (mActionPopupWindow == null) {
mActionPopupWindow = new ActionPopupWindow(this, button);
}
if (mActionPopupShower == null) {
mActionPopupShower = new Runnable() {
public void run() {
try {
mActionPopupWindow.show();
} catch (Throwable th) {
th.printStackTrace();
}
}
};
} else {
mView.removeCallbacks(mActionPopupShower);
}
mView.postDelayed(mActionPopupShower, delay);
}
protected void hideActionPopupWindow() {
if (mActionPopupShower != null) {
mView.removeCallbacks(mActionPopupShower);
}
if (mActionPopupWindow != null) {
mActionPopupWindow.hide();
}
}
public boolean isShowing() {
return mContainer.isShowing();
}
private boolean isVisible() {
// Always show a dragging handle.
if (mIsDragging) {
return true;
}
return mView.getVisibility() == View.VISIBLE;
}
public abstract int getCurrentCursorOffset();
protected abstract void updateSelection(int offset);
public abstract void updatePosition(float x, float y);
protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
mPositionX = mView.getWidth();
mPositionY = mView.getHeight();
mPositionHasChanged = true;
}
public void updatePosition(int parentPositionX, int parentPositionY,
boolean parentPositionChanged, boolean parentScrolled) {
positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
if (parentPositionChanged || mPositionHasChanged) {
if (mIsDragging) {
// Update touchToWindow offset in case of parent scrolling while dragging
if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
mTouchToWindowOffsetX += parentPositionX - mLastParentX;
mTouchToWindowOffsetY += parentPositionY - mLastParentY;
mLastParentX = parentPositionX;
mLastParentY = parentPositionY;
}
onHandleMoved();
}
if (isVisible()) {
final int positionX = parentPositionX + mPositionX;
final int positionY = parentPositionY + mPositionY;
/*
mWindowPosX = positionX;
mWindowPosY = positionY;
*/
if (isShowing()) {
mContainer.update(positionX, positionY, -1, -1);
} else {
mContainer.showAtLocation(mView, Gravity.NO_GRAVITY,
positionX, positionY);
}
} else {
if (isShowing()) {
dismiss();
}
}
mPositionHasChanged = false;
}
}
private int getHorizontalOffset() {
final int width = getPreferredWidth();
final int drawWidth = mDrawable.getIntrinsicWidth();
final int left;
switch (mHorizontalGravity) {
case Gravity.LEFT:
left = 0;
break;
default:
case Gravity.CENTER:
left = (width - drawWidth) / 2;
break;
case Gravity.RIGHT:
left = width - drawWidth;
break;
}
return left;
}
protected int getCursorOffset() {
return 0;
}
public boolean isDragging() {
return mIsDragging;
}
void onHandleMoved() {
hideActionPopupWindow();
}
public void onDetached() {
hideActionPopupWindow();
}
}

View file

@ -1,142 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.util.*;
import android.view.*;
import android.view.ViewGroup.*;
import android.widget.*;
public abstract class PinnedPopupWindow implements ViewPositionListener {
protected PopupWindow mPopupWindow;
protected ViewGroup mContentView;
int mPositionX, mPositionY;
protected HandleView mHandleView;
protected abstract void createPopupWindow();
protected abstract void initContentView();
protected abstract int getTextOffset();
protected abstract int getVerticalLocalPosition(int line);
protected abstract int clipVertically(int positionY);
public PinnedPopupWindow(HandleView handleView) {
mHandleView = handleView;
createPopupWindow();
mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
initContentView();
LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
mContentView.setLayoutParams(wrapContent);
mPopupWindow.setContentView(mContentView);
}
public void show() {
mHandleView.getPositionListener().addSubscriber(this, false /* offset is fixed */);
computeLocalPosition();
final PositionListener positionListener = mHandleView.getPositionListener();
updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
}
protected void measureContent() {
final DisplayMetrics displayMetrics = mHandleView.getResources().getDisplayMetrics();
mContentView.measure(
View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
View.MeasureSpec.AT_MOST));
}
/* The popup window will be horizontally centered on the getTextOffset() and vertically
* positioned according to viewportToContentHorizontalOffset.
*
* This method assumes that mContentView has properly been measured from its content. */
private void computeLocalPosition() {
measureContent();
/*
final int width = mContentView.getMeasuredWidth();
final int offset = getTextOffset();
mPositionX = (int) (mTextView.getLayout().getPrimaryHorizontal(offset) - width / 2.0f);
mPositionX += mTextView.viewportToContentHorizontalOffset();
final int line = mTextView.getLayout().getLineForOffset(offset);
mPositionY = getVerticalLocalPosition(line);
mPositionY += mTextView.viewportToContentVerticalOffset();
*/
/*
mPositionX = (int) mHandleView.mView.getTranslationX();
mPositionY = (int) mHandleView.mView.getTranslationY();
*/
mPositionY = mHandleView.mView.getHeight();
}
private void updatePosition(int parentPositionX, int parentPositionY) {
int positionX = parentPositionX + mPositionX;
int positionY = parentPositionY + mPositionY;
positionY = clipVertically(positionY);
// Horizontal clipping
final DisplayMetrics displayMetrics = mHandleView.getResources().getDisplayMetrics();
final int width = mContentView.getMeasuredWidth();
positionX = Math.min(displayMetrics.widthPixels - width, positionX);
positionX = Math.max(0, positionX);
if (isShowing()) {
mPopupWindow.update(positionX, positionY, -1, -1);
} else {
mPopupWindow.showAtLocation(mHandleView, Gravity.NO_GRAVITY,
positionX, positionY);
}
}
public void hide() {
mPopupWindow.dismiss();
mHandleView.getPositionListener().removeSubscriber(this);
}
@Override
public void updatePosition(int parentPositionX, int parentPositionY,
boolean parentPositionChanged, boolean parentScrolled) {
// Either parentPositionChanged or parentScrolled is true, check if still visible
if (isShowing()) {
if (parentScrolled) computeLocalPosition();
updatePosition(parentPositionX, parentPositionY);
} else {
hide();
}
}
public boolean isShowing() {
return mPopupWindow.isShowing();
}
}

View file

@ -1,119 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.view.*;
public class PositionListener implements ViewTreeObserver.OnPreDrawListener {
// 3 handles
// 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
// 1 CursorAnchorInfoNotifier
private final int MAXIMUM_NUMBER_OF_LISTENERS = 7;
private ViewPositionListener[] mPositionListeners =
new ViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
private boolean mPositionHasChanged = true;
// Absolute position of the TextView with respect to its parent window
private int mPositionX, mPositionY;
private int mNumberOfListeners;
private boolean mScrollHasChanged;
final int[] mTempCoords = new int[2];
private View mView;
public PositionListener(View view) {
mView = view;
}
public void addSubscriber(ViewPositionListener positionListener, boolean canMove) {
if (mNumberOfListeners == 0) {
updatePosition();
ViewTreeObserver vto = mView.getViewTreeObserver();
vto.addOnPreDrawListener(this);
}
int emptySlotIndex = -1;
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
ViewPositionListener listener = mPositionListeners[i];
if (listener == positionListener) {
return;
} else if (emptySlotIndex < 0 && listener == null) {
emptySlotIndex = i;
}
}
mPositionListeners[emptySlotIndex] = positionListener;
mCanMove[emptySlotIndex] = canMove;
mNumberOfListeners++;
}
public void removeSubscriber(ViewPositionListener positionListener) {
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
if (mPositionListeners[i] == positionListener) {
mPositionListeners[i] = null;
mNumberOfListeners--;
break;
}
}
if (mNumberOfListeners == 0) {
ViewTreeObserver vto = mView.getViewTreeObserver();
vto.removeOnPreDrawListener(this);
}
}
public int getPositionX() {
return mPositionX;
}
public int getPositionY() {
return mPositionY;
}
@Override
public boolean onPreDraw() {
updatePosition();
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) {
ViewPositionListener positionListener = mPositionListeners[i];
if (positionListener != null) {
positionListener.updatePosition(mPositionX, mPositionY,
mPositionHasChanged, mScrollHasChanged);
}
}
}
mScrollHasChanged = false;
return true;
}
private void updatePosition() {
mView.getLocationInWindow(mTempCoords);
mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY;
mPositionX = mTempCoords[0];
mPositionY = mTempCoords[1];
}
public void onScrollChanged() {
mScrollHasChanged = true;
}
}

View file

@ -1,75 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.graphics.drawable.*;
import android.view.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
public class SelectionEndHandleView extends HandleView {
public SelectionEndHandleView(ControlButton view) {
super(view);
}
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
if (isRtlRun) {
return (drawable.getIntrinsicWidth() * 3) / 4;
} else {
return drawable.getIntrinsicWidth() / 4;
}
}
@Override
protected int getHorizontalGravity(boolean isRtlRun) {
return isRtlRun ? Gravity.LEFT : Gravity.RIGHT;
}
@Override
public int getCurrentCursorOffset() {
return 0; // mView.getSelectionEnd();
}
public void show(ControlButton button){
super.show();
showActionPopupWindow(0, button);
}
@Override
public void updateSelection(int offset) {
// Selection.setSelection((Spannable) mView.getText(), mView.getSelectionStart(), offset);
updateDrawable();
}
@Override
public void updatePosition(float x, float y) {
// updatePosition((int) x, (int) y, false, false);
positionAtCursorOffset(0, false);
}
@Override
public boolean onLongClick(View view) {
//TODO stub
return false;
}
}

View file

@ -1,24 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class has been splited from android/widget/Editor$HandleView.java
*/
package net.kdt.pojavlaunch.customcontrols.handleview;
public interface ViewPositionListener {
void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled);
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19.44,12.99l-0.01,0.02c0.04,-0.33 0.08,-0.67 0.08,-1.01 0,-0.34 -0.03,-0.66 -0.07,-0.99l0.01,0.02 2.44,-1.92 -2.43,-4.22 -2.87,1.16 0.01,0.01c-0.52,-0.4 -1.09,-0.74 -1.71,-1h0.01L14.44,2H9.57l-0.44,3.07h0.01c-0.62,0.26 -1.19,0.6 -1.71,1l0.01,-0.01 -2.88,-1.17 -2.44,4.22 2.44,1.92 0.01,-0.02c-0.04,0.33 -0.07,0.65 -0.07,0.99 0,0.34 0.03,0.68 0.08,1.01l-0.01,-0.02 -2.1,1.65 -0.33,0.26 2.43,4.2 2.88,-1.15 -0.02,-0.04c0.53,0.41 1.1,0.75 1.73,1.01h-0.03L9.58,22h4.85s0.03,-0.18 0.06,-0.42l0.38,-2.65h-0.01c0.62,-0.26 1.2,-0.6 1.73,-1.01l-0.02,0.04 2.88,1.15 2.43,-4.2s-0.14,-0.12 -0.33,-0.26l-2.11,-1.66zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

View file

@ -44,6 +44,13 @@
android:inputType="textFilter|textImeMultiLine|textAutoComplete|textAutoCorrect"
tools:ignore="TouchTargetSizeCheck" />
<net.kdt.pojavlaunch.customcontrols.handleview.DrawerPullButton
android:id="@+id/drawer_button"
android:layout_width="24dp"
android:layout_height="12dp"
android:elevation="10dp"
android:layout_gravity="center_horizontal"/>
</net.kdt.pojavlaunch.customcontrols.ControlLayout>
<com.kdt.LoggerView

View file

@ -7,15 +7,28 @@
android:layout_alignParentRight="true"
android:id="@+id/customctrl_drawerlayout">
<RelativeLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content">
<net.kdt.pojavlaunch.customcontrols.handleview.DrawerPullButton
android:id="@+id/drawer_button"
android:layout_width="32dp"
android:layout_height="16dp"
android:elevation="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:text="@string/hint_control_mapping"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"/>
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="@string/hint_control_mapping"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<net.kdt.pojavlaunch.customcontrols.ControlLayout
android:id="@+id/customctrl_controllayout"
@ -24,7 +37,7 @@
</net.kdt.pojavlaunch.customcontrols.ControlLayout>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<ListView
android:id="@+id/customctrl_navigation_view"

View file

@ -1,69 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent"
android:layout_height="match_parent">
<net.kdt.pojavlaunch.colorselector.HueView
android:id="@+id/color_selector_hue_view"
android:layout_width="0dp"
android:layout_height="25dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/color_selector_rectangle_view" />
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/_230sdp"
android:layout_height="match_parent"
android:background="@color/background_app"
android:paddingHorizontal="@dimen/_5sdp"
android:paddingVertical="@dimen/_5sdp"
android:id="@+id/color_picker_layout"
android:layout_marginVertical="@dimen/_14sdp">
<net.kdt.pojavlaunch.colorselector.SVRectangleView
android:id="@+id/color_selector_rectangle_view"
android:layout_width="0dp"
android:layout_height="150dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:focusable="true"
app:layout_constraintEnd_toStartOf="@+id/color_selector_hex_edit"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="@+id/color_selector_alpha_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<net.kdt.pojavlaunch.colorselector.AlphaView
android:id="@+id/color_selector_alpha_view"
android:layout_width="0dp"
android:layout_height="25dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/color_selector_hue_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/color_selector_hue_view" />
<EditText
android:id="@+id/color_selector_hex_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:hint="@string/color_default_hex"
android:importantForAutofill="no"
android:inputType="text"
android:minHeight="48dp"
android:typeface="monospace"
app:layout_constraintBottom_toTopOf="@+id/color_selector_hue_view"
app:layout_constraintEnd_toEndOf="parent" />
<net.kdt.pojavlaunch.colorselector.ColorSideBySideView
android:id="@+id/color_selector_color_view"
android:layout_width="0dp"
android:layout_height="94dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/color_selector_rectangle_view"
app:layout_constraintStart_toStartOf="@+id/color_selector_rectangle_view"
app:layout_constraintTop_toBottomOf="@+id/color_selector_rectangle_view" />
<EditText
android:id="@+id/color_selector_hex_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/color_default_hex"
android:gravity="center"
android:importantForAutofill="no"
android:inputType="text"
android:minHeight="48dp"
android:typeface="monospace"
app:layout_constraintEnd_toEndOf="@+id/color_selector_color_view"
app:layout_constraintStart_toStartOf="@+id/color_selector_color_view"
app:layout_constraintTop_toBottomOf="@+id/color_selector_color_view" />
<net.kdt.pojavlaunch.colorselector.AlphaView
android:id="@+id/color_selector_alpha_view"
android:layout_width="25dp"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="@+id/color_selector_hue_view"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<net.kdt.pojavlaunch.colorselector.HueView
android:id="@+id/color_selector_hue_view"
android:layout_width="25dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/color_selector_hex_edit"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/color_selector_rectangle_view"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,13 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<com.kdt.DefocusableScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<com.kdt.DefocusableScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="500dp"
android:layout_width="@dimen/_230sdp"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/background_app"
android:layout_marginVertical="@dimen/_14sdp"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/edit_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/_5sdp"
android:paddingVertical="@dimen/_5sdp"
app:layout_optimizationLevel="standard|dimensions|chains">
@ -15,129 +22,133 @@
<TextView
android:id="@+id/editName_textView"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="@string/global_name"
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"/>
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/editName_editText"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:hint="Button Name"
android:imeOptions="flagNoExtractUi"
android:maxLines="1"
app:layout_constraintStart_toEndOf="@+id/editName_textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/editName_textView"
app:layout_constraintBottom_toBottomOf="@+id/editName_textView"
/>
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="45dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/customctrl_size"
android:paddingEnd="5dp"
android:text="@string/customctrl_size"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editName_textView"
app:layout_constraintTop_toBottomOf="@id/editName_editText"
tools:visibility="visible" />
<EditText
android:id="@+id/editSize_editTextX"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_width="140dp"
android:layout_height="wrap_content"
android:gravity="center"
android:imeOptions="flagNoExtractUi"
android:inputType="numberDecimal"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toEndOf="@id/editSize_textView"
app:layout_constraintTop_toTopOf="@id/editSize_textView"
app:layout_constraintBottom_toBottomOf="@id/editSize_textView"
app:layout_constraintEnd_toStartOf="@id/editSize_editTextY"/>
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_textView"
app:layout_constraintBottom_toBottomOf="@id/editSize_textView"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintTop_toTopOf="@+id/editSize_editTextX" />
<TextView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="x"
android:gravity="center"
android:text="x"
app:layout_constraintStart_toEndOf="@id/editSize_editTextX"
app:layout_constraintBottom_toBottomOf="@+id/editSize_editTextX"
app:layout_constraintEnd_toStartOf="@id/editSize_editTextY"
app:layout_constraintTop_toTopOf="@id/editSize_textView"
app:layout_constraintBottom_toBottomOf="@id/editSize_textView" />
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="70dp"
android:text="@string/customctrl_mapping"
android:paddingTop="8dp"
android:layout_height="wrap_content"
android:paddingEnd="5dp"
android:text="@string/customctrl_mapping"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editSize_textView"/>
app:layout_constraintTop_toBottomOf="@+id/editSize_editTextX" />
<Spinner
android:id="@+id/editMapping_spinner_1"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="30dp"
android:background="@android:color/transparent"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/editMapping_spinner_3"
app:layout_constraintEnd_toStartOf="@id/editMapping_spinner_2"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintVertical_chainStyle="spread"
app:layout_constraintStart_toEndOf="@id/editMapping_textView"
app:layout_constraintTop_toTopOf="@id/editMapping_textView"
app:layout_constraintEnd_toStartOf="@id/editMapping_spinner_2"
app:layout_constraintBottom_toTopOf="@id/editMapping_spinner_3"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editMapping_textView"
app:layout_constraintVertical_chainStyle="spread" />
<TextView
android:id="@+id/editMapping_plus_1"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="+"
android:gravity="center"
android:text="+"
app:layout_constraintStart_toEndOf="@id/editMapping_spinner_1"
app:layout_constraintEnd_toStartOf="@id/editMapping_spinner_2"
app:layout_constraintTop_toTopOf="@id/editMapping_textView"
app:layout_constraintBottom_toBottomOf="@id/editMapping_spinner_1"
/>
app:layout_constraintBottom_toBottomOf="@+id/editMapping_spinner_1"
app:layout_constraintEnd_toEndOf="@+id/editMapping_spinner_1"
app:layout_constraintTop_toTopOf="@+id/editMapping_spinner_1" />
<Spinner
android:id="@+id/editMapping_spinner_2"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="30dp"
android:background="@android:color/transparent"
android:gravity="center"
app:layout_constraintStart_toEndOf="@id/editMapping_spinner_1"
app:layout_constraintBottom_toBottomOf="@+id/editMapping_spinner_1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editMapping_textView"
app:layout_constraintBottom_toBottomOf="@id/editMapping_spinner_1"
/>
app:layout_constraintStart_toEndOf="@id/editMapping_spinner_1"
app:layout_constraintTop_toTopOf="@+id/editMapping_spinner_1" />
<TextView
android:id="@+id/editMapping_plus_2"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="+"
android:gravity="center"
android:text="+"
app:layout_constraintBottom_toBottomOf="@id/editMapping_textView"
app:layout_constraintEnd_toEndOf="@id/editMapping_textView"
@ -148,311 +159,237 @@
<Spinner
android:id="@+id/editMapping_spinner_3"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="30dp"
android:background="@android:color/transparent"
android:gravity="center"
app:layout_constraintStart_toEndOf="@id/editMapping_textView"
app:layout_constraintTop_toBottomOf="@id/editMapping_spinner_1"
app:layout_constraintBottom_toBottomOf="@id/editMapping_textView"
app:layout_constraintEnd_toStartOf="@id/editMapping_spinner_4"/>
app:layout_constraintEnd_toStartOf="@id/editMapping_spinner_4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editMapping_spinner_1" />
<TextView
android:id="@+id/editMapping_plus_3"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="+"
android:gravity="center"
android:text="+"
app:layout_constraintBottom_toBottomOf="@id/editMapping_textView"
app:layout_constraintStart_toEndOf="@id/editMapping_spinner_3"
app:layout_constraintEnd_toStartOf="@id/editMapping_spinner_4"
app:layout_constraintTop_toTopOf="@id/editMapping_spinner_4"
/>
app:layout_constraintBottom_toBottomOf="@+id/editMapping_spinner_3"
app:layout_constraintEnd_toEndOf="@+id/editMapping_spinner_3"
app:layout_constraintTop_toBottomOf="@+id/editMapping_spinner_1" />
<Spinner
android:id="@+id/editMapping_spinner_4"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="30dp"
android:background="@android:color/transparent"
android:gravity="center"
app:layout_constraintStart_toEndOf="@id/editMapping_spinner_3"
app:layout_constraintTop_toBottomOf="@id/editMapping_spinner_2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/editMapping_textView"
/>
app:layout_constraintStart_toEndOf="@id/editMapping_spinner_3"
app:layout_constraintTop_toBottomOf="@id/editMapping_spinner_2" />
<!-- ORIENTATION SECTION -->
<TextView
android:id="@+id/editOrientation_textView"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:text="@string/customctrl_orientation"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingEnd="5dp"
android:text="@string/customctrl_orientation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editMapping_textView"
/>
app:layout_constraintTop_toBottomOf="@+id/editMapping_spinner_3" />
<Spinner
android:id="@+id/editOrientation_spinner"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="30dp"
android:background="@android:color/transparent"
app:layout_constraintStart_toEndOf="@id/editOrientation_textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editOrientation_textView"
app:layout_constraintBottom_toBottomOf="@id/editOrientation_textView"
/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editOrientation_textView" />
<!-- TOGGLE SECTION -->
<CheckBox
<Switch
android:id="@+id/checkboxToggle"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_height="30dp"
android:text="@string/customctrl_toggle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editOrientation_textView"
/>
app:layout_constraintTop_toBottomOf="@id/editOrientation_spinner"
tools:ignore="UseSwitchCompatOrMaterialXml" />
<!-- MOUSE PASS THROUGH SECTION -->
<CheckBox
<Switch
android:id="@+id/checkboxPassThrough"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="30dp"
android:text="@string/customctrl_passthru"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/checkboxToggle"
/>
tools:ignore="UseSwitchCompatOrMaterialXml" />
<!-- SWIPEABLE BUTTON SECTION -->
<CheckBox
<Switch
android:id="@+id/checkboxSwipeable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="30dp"
android:text="@string/customctrl_swipeable"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/checkboxPassThrough"
/>
tools:ignore="UseSwitchCompatOrMaterialXml" />
<!-- BACKGROUND COLOR SECTION -->
<TextView
<fr.spse.extended_view.ExtendedTextView
android:id="@+id/editBackgroundColor_textView"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:text="@string/customctrl_background_color"
android:gravity="center"
android:paddingEnd="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/checkboxSwipeable"
/>
<ImageButton
android:id="@+id/editBackgroundColor_imageButton"
android:background="?attr/selectableItemBackground"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginHorizontal="50dp"
android:layout_marginVertical="5dp"
android:layout_height="35dp"
android:gravity="center_vertical"
android:paddingEnd="5dp"
android:text="@string/customctrl_background_color"
android:drawableEnd="@drawable/spinner_arrow_right"
app:drawableEndSize="20dp"
app:drawableEndIntegerScaling="true"
android:background="#FFFFFFFF"
app:layout_constraintStart_toEndOf="@id/editBackgroundColor_textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editBackgroundColor_textView"
app:layout_constraintBottom_toBottomOf="@id/editBackgroundColor_textView"
/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/checkboxSwipeable" />
<!-- STROKE WIDTH -->
<TextView
android:id="@+id/editStrokeWidth_textView"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:text="@string/customctrl_stroke_width"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingEnd="5dp"
android:text="@string/customctrl_stroke_width"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editBackgroundColor_textView"/>
app:layout_constraintTop_toBottomOf="@id/editStrokeColor_textView" />
<SeekBar
android:id="@+id/editStrokeWidth_seekbar"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/editStrokeWidth_textView"
app:layout_constraintTop_toTopOf="@id/editStrokeWidth_textView"
app:layout_constraintBottom_toBottomOf="@id/editStrokeWidth_textView"
app:layout_constraintEnd_toStartOf="@id/editStrokeWidth_textView_percent"
/>
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:text="100%"
android:gravity="center"
android:text="100%"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editStrokeWidth_textView"
app:layout_constraintBottom_toBottomOf="@id/editStrokeWidth_textView"
/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editStrokeWidth_textView" />
<!-- STROKE COLOR VERSION -->
<TextView
<fr.spse.extended_view.ExtendedTextView
android:id="@+id/editStrokeColor_textView"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:text="@string/customctrl_stroke_color"
android:gravity="center"
android:paddingEnd="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editStrokeWidth_textView"
/>
<ImageButton
android:id="@+id/editStrokeColor_imageButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginHorizontal="50dp"
android:layout_marginVertical="5dp"
android:layout_height="35dp"
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"
android:background="#FFFFFFFF"
app:layout_constraintStart_toEndOf="@id/editStrokeColor_textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editStrokeColor_textView"
app:layout_constraintBottom_toBottomOf="@id/editStrokeColor_textView"
/>
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="35dp"
android:text="@string/customctrl_corner_radius"
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/editStrokeColor_textView"/>
app:layout_constraintTop_toBottomOf="@id/editStrokeWidth_seekbar" />
<SeekBar
android:id="@+id/editCornerRadius_seekbar"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="30dp"
app:layout_constraintStart_toEndOf="@id/editCornerRadius_textView"
app:layout_constraintTop_toTopOf="@id/editCornerRadius_textView"
app:layout_constraintBottom_toBottomOf="@id/editCornerRadius_textView"
app:layout_constraintEnd_toStartOf="@id/editCornerRadius_textView_percent"
/>
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:text="100%"
android:gravity="center"
android:text="100%"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editCornerRadius_textView"
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="35dp"
android:text="@string/customctrl_button_opacity"
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_textView"/>
app:layout_constraintTop_toBottomOf="@+id/editCornerRadius_seekbar" />
<SeekBar
android:id="@+id/editButtonOpacity_seekbar"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="30dp"
app:layout_constraintStart_toEndOf="@id/editButtonOpacity_textView"
app:layout_constraintTop_toTopOf="@id/editButtonOpacity_textView"
app:layout_constraintBottom_toBottomOf="@id/editButtonOpacity_textView"
app:layout_constraintEnd_toStartOf="@id/editButtonOpacity_textView_percent"
/>
app:layout_constraintBottom_toBottomOf="parent"
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:text="100%"
android:gravity="center"
android:text="100%"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editButtonOpacity_textView"
app:layout_constraintBottom_toBottomOf="@id/editButtonOpacity_textView"
/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/editButtonOpacity_textView" />
<!-- DYNAMIC BUTTON SECTION -->
<CheckBox
android:id="@+id/checkboxDynamicPosition"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/customctrl_dynamicpos"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editButtonOpacity_textView"
/>
<TextView
android:id="@+id/editDynamicPositionX_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/customctrl_dynamicpos_x"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/checkboxDynamicPosition"
/>
<EditText
android:id="@+id/editDynamicPositionX_editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editDynamicPositionX_textView"
/>
<TextView
android:id="@+id/editDynamicPositionY_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/customctrl_dynamicpos_y"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editDynamicPositionX_editText"
/>
<EditText
android:id="@+id/editDynamicPositionY_editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editDynamicPositionY_textView"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,9 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textAlignment="inherit"
android:gravity="center"/>

View file

@ -2,4 +2,12 @@
<resources>
<color name="control_button_color">#4CC4C4C4</color>
<color name="control_button_pressed_color">#4D000000</color>
<color name="minebutton_color">#57CC33</color>
<color name="background_app">#181818</color>
<color name="background_status_bar">#242424</color>
<color name="background_bottom_bar">#232323</color>
<color name="primary_text">#FFFFFF</color>
<color name="secondary_text">#B2B2B2</color>
</resources>

View file

@ -15,6 +15,15 @@
<item name="4">@string/customctrl_selectdefault</item>
</string-array>
<string-array name="menu_customcontrol_customactivity">
<item name="0">@string/customctrl_addbutton</item>
<item name="1">@string/customctrl_addbutton_drawer</item>
<item name="2">@string/global_load</item>
<item name="3">@string/global_save</item>
<item name="4">@string/customctrl_selectdefault</item>
<item name="5">@string/customctrl_export</item>
</string-array>
<string-array name="menu_ingame">
<item>@string/control_forceclose</item>
<item>@string/control_viewout</item>

View file

@ -205,6 +205,7 @@
<string name="customctrl_add_subbutton_message">Sub-button n°%d has been added !</string>
<string name="customctrl_selectdefault">Select default Control json</string>
<string name="customctrl_export">Export control</string>
<string name="main_install_jar_file">Install .jar</string>

View file

@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="files" path="/"/>
</paths>