Feat[controls]: Decouple stroke width from button size

This commit is contained in:
Mathias-Boulay 2023-05-16 15:29:59 +02:00 committed by ArtDev
parent fd5f46f243
commit b4a3e18b9a
5 changed files with 290 additions and 222 deletions

View File

@ -35,18 +35,16 @@ public class ControlData {
private static ControlData[] SPECIAL_BUTTONS;
private static List<String> SPECIAL_BUTTON_NAME_ARRAY;
// Internal usage only
public boolean isHideable;
private static WeakReference<ExpressionBuilder> builder = new WeakReference<>(null);
private static WeakReference<ArrayMap<String, String>> conversionMap = new WeakReference<>(null);
static {
buildExpressionBuilder();
buildConversionMap();
}
// Internal usage only
public boolean isHideable;
/**
* Both fields below are dynamic position data, auto updates
* X and Y position, unlike the original one which uses fixed
@ -56,52 +54,18 @@ public class ControlData {
*/
public String dynamicX, dynamicY;
public boolean isDynamicBtn, isToggle, passThruEnabled;
public static ControlData[] getSpecialButtons(){
if (SPECIAL_BUTTONS == null) {
SPECIAL_BUTTONS = new ControlData[]{
new ControlData("Keyboard", new int[]{SPECIALBTN_KEYBOARD}, "${margin} * 3 + ${width} * 2", "${margin}", false),
new ControlData("GUI", new int[]{SPECIALBTN_TOGGLECTRL}, "${margin}", "${bottom} - ${margin}"),
new ControlData("PRI", new int[]{SPECIALBTN_MOUSEPRI}, "${margin}", "${screen_height} - ${margin} * 3 - ${height} * 3"),
new ControlData("SEC", new int[]{SPECIALBTN_MOUSESEC}, "${margin} * 3 + ${width} * 2", "${screen_height} - ${margin} * 3 - ${height} * 3"),
new ControlData("Mouse", new int[]{SPECIALBTN_VIRTUALMOUSE}, "${right}", "${margin}", false),
new ControlData("MID", new int[]{SPECIALBTN_MOUSEMID}, "${margin}", "${margin}"),
new ControlData("SCROLLUP", new int[]{SPECIALBTN_SCROLLUP}, "${margin}", "${margin}"),
new ControlData("SCROLLDOWN", new int[]{SPECIALBTN_SCROLLDOWN}, "${margin}", "${margin}"),
new ControlData("MENU", new int[]{SPECIALBTN_MENU}, "${margin}", "${margin}")
};
}
return SPECIAL_BUTTONS;
}
public static List<String> buildSpecialButtonArray() {
if (SPECIAL_BUTTON_NAME_ARRAY == null) {
List<String> nameList = new ArrayList<>();
for (ControlData btn : getSpecialButtons()) {
nameList.add("SPECIAL_" + btn.name);
}
SPECIAL_BUTTON_NAME_ARRAY = nameList;
Collections.reverse(SPECIAL_BUTTON_NAME_ARRAY);
}
return SPECIAL_BUTTON_NAME_ARRAY;
}
public String name;
private float width; //Dp instead of Px now
private float height; //Dp instead of Px now
public int[] keycodes; //Should store up to 4 keys
public float opacity; //Alpha value from 0 to 1;
public int bgColor;
public int strokeColor;
public int strokeWidth; //0-100%
public float strokeWidth; // Dp instead of % now
public float cornerRadius; //0-100%
public boolean isSwipeable;
public boolean displayInGame;
public boolean displayInMenu;
private float width; //Dp instead of Px now
private float height; //Dp instead of Px now
public ControlData() {
this("button");
@ -148,7 +112,7 @@ public class ControlData {
this(name, keycodes, dynamicX, dynamicY, width, height, isToggle, 1, 0x4D000000, 0xFFFFFFFF, 0, 0, true, true);
}
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, float width, float height, boolean isToggle, float opacity, int bgColor, int strokeColor, int strokeWidth, float cornerRadius, boolean displayInGame, boolean displayInMenu) {
public ControlData(String name, int[] keycodes, String dynamicX, String dynamicY, float width, float height, boolean isToggle, float opacity, int bgColor, int strokeColor, float strokeWidth, float cornerRadius, boolean displayInGame, boolean displayInMenu) {
this.name = name;
this.keycodes = inflateKeycodeArray(keycodes);
this.dynamicX = dynamicX;
@ -186,13 +150,36 @@ public class ControlData {
);
}
public static ControlData[] getSpecialButtons() {
if (SPECIAL_BUTTONS == null) {
SPECIAL_BUTTONS = new ControlData[]{
new ControlData("Keyboard", new int[]{SPECIALBTN_KEYBOARD}, "${margin} * 3 + ${width} * 2", "${margin}", false),
new ControlData("GUI", new int[]{SPECIALBTN_TOGGLECTRL}, "${margin}", "${bottom} - ${margin}"),
new ControlData("PRI", new int[]{SPECIALBTN_MOUSEPRI}, "${margin}", "${screen_height} - ${margin} * 3 - ${height} * 3"),
new ControlData("SEC", new int[]{SPECIALBTN_MOUSESEC}, "${margin} * 3 + ${width} * 2", "${screen_height} - ${margin} * 3 - ${height} * 3"),
new ControlData("Mouse", new int[]{SPECIALBTN_VIRTUALMOUSE}, "${right}", "${margin}", false),
public float insertDynamicPos(String dynamicPos) {
// Insert value to ${variable}
String insertedPos = JSONUtils.insertSingleJSONValue(dynamicPos, fillConversionMap());
new ControlData("MID", new int[]{SPECIALBTN_MOUSEMID}, "${margin}", "${margin}"),
new ControlData("SCROLLUP", new int[]{SPECIALBTN_SCROLLUP}, "${margin}", "${margin}"),
new ControlData("SCROLLDOWN", new int[]{SPECIALBTN_SCROLLDOWN}, "${margin}", "${margin}"),
new ControlData("MENU", new int[]{SPECIALBTN_MENU}, "${margin}", "${margin}")
};
}
// Calculate, because the dynamic position contains some math equations
return calculate(insertedPos);
return SPECIAL_BUTTONS;
}
public static List<String> buildSpecialButtonArray() {
if (SPECIAL_BUTTON_NAME_ARRAY == null) {
List<String> nameList = new ArrayList<>();
for (ControlData btn : getSpecialButtons()) {
nameList.add("SPECIAL_" + btn.name);
}
SPECIAL_BUTTON_NAME_ARRAY = nameList;
Collections.reverse(SPECIAL_BUTTON_NAME_ARRAY);
}
return SPECIAL_BUTTON_NAME_ARRAY;
}
private static float calculate(String math) {
@ -206,34 +193,6 @@ public class ControlData {
return inflatedArray;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean containsKeycode(int keycodeToCheck){
for(int keycode : keycodes)
if(keycodeToCheck == keycode)
return true;
return false;
}
//Getters || setters (with conversion for ease of use)
public float getWidth() {
return Tools.dpToPx(width);
}
public float getHeight(){
return Tools.dpToPx(height);
}
public void setWidth(float widthInPx){
width = Tools.pxToDp(widthInPx);
}
public void setHeight(float heightInPx){
height = Tools.pxToDp(heightInPx);
}
/**
* Create a builder, keep a weak reference to it to use it with all views on first inflation
*/
@ -256,6 +215,7 @@ public class ControlData {
/**
* wrapper for the WeakReference to the expressionField.
*
* @param stringExpression the expression to set.
*/
private static void setExpression(String stringExpression) {
@ -284,9 +244,44 @@ public class ControlData {
conversionMap = new WeakReference<>(keyValueMap);
}
public float insertDynamicPos(String dynamicPos) {
// Insert value to ${variable}
String insertedPos = JSONUtils.insertSingleJSONValue(dynamicPos, fillConversionMap());
// Calculate, because the dynamic position contains some math equations
return calculate(insertedPos);
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean containsKeycode(int keycodeToCheck) {
for (int keycode : keycodes)
if (keycodeToCheck == keycode)
return true;
return false;
}
//Getters || setters (with conversion for ease of use)
public float getWidth() {
return Tools.dpToPx(width);
}
public void setWidth(float widthInPx) {
width = Tools.pxToDp(widthInPx);
}
public float getHeight() {
return Tools.dpToPx(height);
}
public void setHeight(float heightInPx) {
height = Tools.pxToDp(heightInPx);
}
/**
* Fill the conversionMap with controlData dependent values.
* The returned valueMap should NOT be kept in memory.
*
* @return the valueMap to use.
*/
private Map<String, String> fillConversionMap() {

View File

@ -29,6 +29,8 @@ public class LayoutConverter {
layout.save(jsonPath);
return layout;
}else if (layoutJobj.getInt("version") >= 3 && layoutJobj.getInt("version") <= 5) {
return LayoutConverter.convertV3_4Layout(layoutJobj);
} else if (layoutJobj.getInt("version") == 6) {
return Tools.GLOBAL_GSON.fromJson(jsonLayoutData, CustomControls.class);
} else {
return null;
@ -37,6 +39,19 @@ public class LayoutConverter {
throw new JsonSyntaxException("Failed to load", e);
}
}
/**
* Normalize the layout to v5 from v3/4: The stroke width is no longer dependant on the button size
*/
public static CustomControls convertV3_4Layout(JSONObject oldLayoutJson) {
CustomControls layout = Tools.GLOBAL_GSON.fromJson(oldLayoutJson.toString(), CustomControls.class);
convertStrokeWidth(layout);
layout.version = 5;
return layout;
}
public static CustomControls convertV2Layout(JSONObject oldLayoutJson) throws JSONException {
CustomControls layout = Tools.GLOBAL_GSON.fromJson(oldLayoutJson.toString(), CustomControls.class);
JSONArray layoutMainArray = oldLayoutJson.getJSONArray("mControlDataList");
@ -74,9 +89,12 @@ public class LayoutConverter {
}
layout.mDrawerDataList.add(n_button);
}
convertStrokeWidth(layout);
layout.version = 3;
return layout;
}
public static CustomControls convertV1Layout(JSONObject oldLayoutJson) throws JSONException {
CustomControls empty = new CustomControls();
JSONArray layoutMainArray = oldLayoutJson.getJSONArray("mControlDataList");
@ -108,11 +126,22 @@ public class LayoutConverter {
n_button.setWidth(button.getInt("width"));
n_button.bgColor = 0x4d000000;
n_button.strokeWidth = 0;
if(button.getBoolean("isRound")) { n_button.cornerRadius = 35f; }
if (button.getBoolean("isRound")) {
n_button.cornerRadius = 35f;
}
int next_idx = 0;
if(button.getBoolean("holdShift")) { keycodes[next_idx] = LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT; next_idx++; }
if(button.getBoolean("holdCtrl")) { keycodes[next_idx] = LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL; next_idx++; }
if(button.getBoolean("holdAlt")) { keycodes[next_idx] = LwjglGlfwKeycode.GLFW_KEY_LEFT_ALT; next_idx++; }
if (button.getBoolean("holdShift")) {
keycodes[next_idx] = LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT;
next_idx++;
}
if (button.getBoolean("holdCtrl")) {
keycodes[next_idx] = LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL;
next_idx++;
}
if (button.getBoolean("holdAlt")) {
keycodes[next_idx] = LwjglGlfwKeycode.GLFW_KEY_LEFT_ALT;
next_idx++;
}
keycodes[next_idx] = button.getInt("keycode");
n_button.keycodes = keycodes;
empty.mControlDataList.add(n_button);
@ -121,4 +150,29 @@ public class LayoutConverter {
empty.version = 3;
return empty;
}
/**
* Convert the layout stroke width to the V5 form
*/
private static void convertStrokeWidth(CustomControls layout) {
for (ControlData data : layout.mControlDataList) {
data.strokeWidth = Tools.pxToDp(computeStrokeWidth(data.strokeWidth, data.getWidth(), data.getHeight()));
}
for (ControlDrawerData data : layout.mDrawerDataList) {
data.properties.strokeWidth = Tools.pxToDp(computeStrokeWidth(data.properties.strokeWidth, data.properties.getWidth(), data.properties.getHeight()));
for (ControlData subButtonData : data.buttonProperties) {
subButtonData.strokeWidth = Tools.pxToDp(computeStrokeWidth(subButtonData.strokeWidth, data.properties.getWidth(), data.properties.getWidth()));
}
}
}
/**
* Convert a size percentage into a px size, used by older layout versions
*/
static int computeStrokeWidth(float widthInPercent, float width, float height) {
float maxSize = Math.max(width, height);
return (int) ((maxSize / 2) * (widthInPercent / 100));
}
}

View File

@ -30,22 +30,32 @@ import org.lwjgl.glfw.CallbackBridge;
public interface ControlInterface extends View.OnLongClickListener, GrabListener {
View getControlView();
ControlData getProperties();
/** Remove the button presence from the CustomControl object
default void setProperties(ControlData properties) {
setProperties(properties, true);
}
/**
* 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
/**
* 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 */
/**
* Load the values and hide non useful forms
*/
void loadEditValues(EditControlPopup editControlPopup);
@Override
@ -58,7 +68,9 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
return (ControlLayout) getControlView().getParent();
}
/** Apply conversion steps for when the view is created */
/**
* 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);
@ -74,10 +86,6 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
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) {
@ -88,19 +96,22 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
// Recycle layout params
ViewGroup.LayoutParams params = getControlView().getLayoutParams();
if(params == null) params = new FrameLayout.LayoutParams((int) properties.getWidth(), (int) properties.getHeight());
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 */
/**
* 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.setStroke((int) Tools.dpToPx(getProperties().strokeWidth), getProperties().strokeColor);
gd.setCornerRadius(computeCornerRadius(getProperties().cornerRadius));
getControlView().setBackground(gd);
@ -108,6 +119,7 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
/**
* Apply the dynamic equation on the x axis.
*
* @param dynamicX The equation to compute the position from
*/
default void setDynamicX(String dynamicX) {
@ -117,6 +129,7 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
/**
* Apply the dynamic equation on the y axis.
*
* @param dynamicY The equation to compute the position from
*/
default void setDynamicY(String dynamicY) {
@ -126,6 +139,7 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
/**
* 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
*/
@ -139,6 +153,7 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
/**
* 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
*/
@ -150,7 +165,9 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
}
}
/** Regenerate and apply coordinates with supposedly modified properties */
/**
* Regenerate and apply coordinates with supposedly modified properties
*/
default void regenerateDynamicCoordinates() {
getProperties().dynamicX = generateDynamicX(getControlView().getX());
getProperties().dynamicY = generateDynamicY(getControlView().getY());
@ -160,8 +177,9 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
/**
* Do a pre-conversion of an equation using values from a button,
* so the variables can be used for another button
*
* <p>
* 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.
@ -174,13 +192,10 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
.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 */
/**
* 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);
@ -210,7 +225,7 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
* 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.
*
* <p>
* The new position is always dynamic, thus replacing previous dynamic positions
*
* @param x Coordinate on the x axis
@ -272,7 +287,9 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
setDynamicY(dynamicY);
}
/** Wrapper for multiple injections at once */
/**
* Wrapper for multiple injections at once
*/
default void injectBehaviors() {
injectProperties();
injectTouchEventBehavior();
@ -280,7 +297,9 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
injectGrabListenerBehavior();
}
/** Inject the grab listener, remove it when the view is gone */
/**
* Inject the grab listener, remove it when the view is gone
*/
default void injectGrabListenerBehavior() {
if (getControlView() == null) {
Log.e(ControlInterface.class.toString(), "Failed to inject grab listener behavior !");
@ -308,7 +327,9 @@ public interface ControlInterface extends View.OnLongClickListener, GrabListener
getControlView().post(() -> getControlView().setTranslationZ(10));
}
/** Inject a touch listener on the view to make editing controls straight forward */
/**
* Inject a touch listener on the view to make editing controls straight forward
*/
default void injectTouchEventBehavior() {
getControlView().setOnTouchListener(new View.OnTouchListener() {
private boolean mCanTriggerLongClick = true;

View File

@ -26,24 +26,26 @@ import io.github.controlwear.virtual.joystick.android.JoystickView;
@SuppressLint("ViewConstructor")
public class ControlJoystick extends JoystickView implements ControlInterface {
public ControlJoystick(ControlLayout parent, ControlData data) {
super(parent.getContext());
init(data, parent);
}
public final static int DIRECTION_FORWARD_LOCK = 8;
private ControlData mControlData;
private int mLastDirectionInt = GamepadJoystick.DIRECTION_NONE;
private int mCurrentDirectionInt = GamepadJoystick.DIRECTION_NONE;
// Directions keycode
private final int[] mDirectionForwardLock = new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL};
private final int[] mDirectionForward = new int[]{LwjglGlfwKeycode.GLFW_KEY_W};
private final int[] mDirectionRight = new int[]{LwjglGlfwKeycode.GLFW_KEY_D};
private final int[] mDirectionBackward = new int[]{LwjglGlfwKeycode.GLFW_KEY_S};
private final int[] mDirectionLeft = new int[]{LwjglGlfwKeycode.GLFW_KEY_A};
private ControlData mControlData;
private int mLastDirectionInt = GamepadJoystick.DIRECTION_NONE;
private int mCurrentDirectionInt = GamepadJoystick.DIRECTION_NONE;
public ControlJoystick(ControlLayout parent, ControlData data) {
super(parent.getContext());
init(data, parent);
}
private static void sendInput(int[] keys, boolean isDown) {
for (int key : keys) {
CallbackBridge.sendKeyPress(key, CallbackBridge.getCurrentMods(), isDown);
}
}
private void init(ControlData data, ControlLayout layout) {
mControlData = data;
@ -75,7 +77,9 @@ public class ControlJoystick extends JoystickView implements ControlInterface {
}
@Override
public View getControlView() {return this;}
public View getControlView() {
return this;
}
@Override
public ControlData getProperties() {
@ -107,7 +111,7 @@ public class ControlJoystick extends JoystickView implements ControlInterface {
@Override
public void setBackground() {
setBorderWidth(computeStrokeWidth(getProperties().strokeWidth));
setBorderWidth((int) Tools.dpToPx(getProperties().strokeWidth));
setBorderColor(getProperties().strokeColor);
setBackgroundColor(getProperties().bgColor);
}
@ -161,10 +165,4 @@ public class ControlJoystick extends JoystickView implements ControlInterface {
}
}
private static void sendInput(int[] keys, boolean isDown){
for(int key : keys){
CallbackBridge.sendKeyPress(key, CallbackBridge.getCurrentMods(), isDown);
}
}
}

View File

@ -269,11 +269,11 @@ public class EditControlPopup {
mHeightEditText.setText(String.valueOf(data.getHeight()));
mAlphaSeekbar.setProgress((int) (data.opacity * 100));
mStrokeWidthSeekbar.setProgress(data.strokeWidth);
mStrokeWidthSeekbar.setProgress((int) data.strokeWidth * 10);
mCornerRadiusSeekbar.setProgress((int) data.cornerRadius);
setPercentageText(mAlphaPercentTextView, (int) (data.opacity * 100));
setPercentageText(mStrokePercentTextView, data.strokeWidth);
setPercentageText(mStrokePercentTextView, (int) data.strokeWidth * 10);
setPercentageText(mCornerRadiusPercentTextView, (int) data.cornerRadius);
mToggleSwitch.setChecked(data.isToggle);
@ -498,7 +498,7 @@ public class EditControlPopup {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (internalChanges) return;
mCurrentlyEditedButton.getProperties().strokeWidth = mStrokeWidthSeekbar.getProgress();
mCurrentlyEditedButton.getProperties().strokeWidth = mStrokeWidthSeekbar.getProgress() / 10F;
mCurrentlyEditedButton.setBackground();
setPercentageText(mStrokePercentTextView, progress);
}