org.mini2Dx.ui.layout.FlexLayoutRuleset Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2019 See AUTHORS file
*
* 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.
******************************************************************************/
package org.mini2Dx.ui.layout;
import org.mini2Dx.core.exception.MdxException;
import org.mini2Dx.core.input.GamePadType;
import org.mini2Dx.gdx.math.MathUtils;
import org.mini2Dx.gdx.utils.Array;
import org.mini2Dx.ui.InputSource;
import org.mini2Dx.ui.render.ParentRenderNode;
import org.mini2Dx.ui.render.RenderNode;
import java.util.*;
public class FlexLayoutRuleset extends LayoutRuleset {
public static final String DEFAULT_RULESET = "flex-column:xs-12c,xs-auto";
protected static final String AUTO = "auto";
protected static final String PIXEL_SUFFIX = "px";
protected static final String COLUMN_SUFFIX = "c";
protected static final String EMPTY_STRING = "";
protected final String rules;
protected final Map widthRules = new HashMap();
protected final Map heightRules = new HashMap();
protected final Set hiddenByInput = new HashSet();
protected final Set hiddenByGamePadType = new HashSet();
protected final Map offsetXRules = new HashMap();
protected final Map offsetYRules = new HashMap();
private final FlexDirection flexDirection;
private boolean hiddenByInputSource = false;
private SizeRule currentWidthRule = null;
private SizeRule currentHeightRule = null;
private OffsetRule currentOffsetXRule = null;
private OffsetRule currentOffsetYRule = null;
/**
* Constructor
* @param flexDirection The {@link FlexDirection}
* @param rules The ruleset, e.g. flex-column:xs-12c xs-offset-4c sm-500px sm-offset-20px,auto
* @param ruleValue The part of the ruleset after :
*/
public FlexLayoutRuleset(FlexDirection flexDirection, String rules, String ruleValue) {
super();
this.rules = rules;
this.flexDirection = flexDirection;
String [] components = ruleValue.split(",");
switch(components.length) {
case 1: {
//Horizontal only
String[] rule = components[0].trim().split(" ");
for (int i = 0; i < rule.length; i++) {
String[] ruleDetails = rule[i].split("-");
switch (ruleDetails.length) {
case 1:
throw new MdxException("Invalid flex value '" + rule[i] + "'. Perhaps you forgot a size prefix, e.g. xs-");
case 2:
// e.g. xs-12, hidden-gamepad, visible-touchscreen,
// hidden-keyboardmouse
storeSizeRule(true, widthRules, ruleDetails);
break;
case 3:
// e.g. xs-offset-12, hidden-gamepad-ps4
storeOffsetRule(true, offsetXRules, ruleDetails);
break;
}
}
storeSizeRule(false, heightRules, "xs-auto".split("-"));
break;
}
case 2: {
{
//Horizontal
String[] rule = components[0].trim().split(" ");
for (int i = 0; i < rule.length; i++) {
String[] ruleDetails = rule[i].split("-");
switch (ruleDetails.length) {
case 1:
throw new MdxException("Invalid flex value '" + rule[i] + "'. Perhaps you forgot a size prefix, e.g. xs-");
case 2:
// e.g. xs-12, hidden-gamepad, visible-touchscreen,
// hidden-keyboardmouse
storeSizeRule(true, widthRules, ruleDetails);
break;
case 3:
// e.g. xs-offset-12, hidden-gamepad-ps4
storeOffsetRule(true, offsetXRules, ruleDetails);
break;
}
}
}
{
//Vertical
String[] rule = components[1].trim().split(" ");
for (int i = 0; i < rule.length; i++) {
String[] ruleDetails = rule[i].split("-");
switch (ruleDetails.length) {
case 1:
throw new MdxException("Invalid flex value '" + rule[i] + "'. Perhaps you forgot a size prefix, e.g. xs-");
case 2:
// e.g. xs-12, hidden-gamepad, visible-touchscreen,
// hidden-keyboardmouse
storeSizeRule(false, heightRules, ruleDetails);
break;
case 3:
// e.g. xs-offset-12, hidden-gamepad-ps4
storeOffsetRule(false, offsetYRules, ruleDetails);
break;
}
}
}
}
default:
break;
}
finaliseRuleset(widthRules, offsetXRules);
finaliseRuleset(heightRules, offsetYRules);
}
private void storeSizeRule(boolean horizontalRuleset, Map sizeRules, String[] ruleDetails) {
switch (ruleDetails[0].toLowerCase()) {
case "hidden": {
if(!horizontalRuleset) {
throw new MdxException("hidden-* rules can only be applied to horizontal rulesets");
}
switch (InputSource.fromFriendlyString(ruleDetails[1])) {
case CONTROLLER:
hiddenByInput.add(InputSource.CONTROLLER);
break;
case KEYBOARD_MOUSE:
hiddenByInput.add(InputSource.KEYBOARD_MOUSE);
break;
case TOUCHSCREEN:
hiddenByInput.add(InputSource.TOUCHSCREEN);
break;
}
break;
}
default:
ScreenSize screenSize = ScreenSize.fromString(ruleDetails[0].trim());
if (ruleDetails[1].equalsIgnoreCase(AUTO)) {
if(horizontalRuleset) {
throw new MdxException("Invalid size - cannot use auto size for horizontal size rules. Must end be columns (c) or pixels (px)");
}
sizeRules.put(screenSize, new AutoSizeRule());
} else if (ruleDetails[1].endsWith(PIXEL_SUFFIX)) {
sizeRules.put(screenSize,
new AbsoluteSizeRule(Float.parseFloat(ruleDetails[1].replace(PIXEL_SUFFIX, EMPTY_STRING).trim())));
} else if (ruleDetails[1].endsWith(COLUMN_SUFFIX)) {
if(!horizontalRuleset) {
throw new MdxException("Invalid size - cannot use column size for vertical size rules. Must be pixel (px) or auto");
}
sizeRules.put(screenSize,
new ResponsiveSizeRule(Integer.parseInt(ruleDetails[1].replace(COLUMN_SUFFIX, EMPTY_STRING).trim())));
} else {
throw new MdxException("Invalid size - must end with c (columns) or px (pixels");
}
break;
}
}
private void storeOffsetRule(boolean horizontalRuleset, Map offsetRules, String[] ruleDetails) {
switch (ruleDetails[0].toLowerCase()) {
case "hidden": {
if(!horizontalRuleset) {
throw new MdxException("hidden-* rules can only be applied to horizontal rulesets");
}
switch (InputSource.fromFriendlyString(ruleDetails[1])) {
case CONTROLLER:
GamePadType controllerType = GamePadType.fromFriendlyString(ruleDetails[2]);
switch(controllerType) {
case UNKNOWN:
break;
default:
hiddenByGamePadType.add(controllerType);
break;
}
break;
default:
throw new MdxException("Invalid rule " + ruleDetails[0] + "-" + ruleDetails[1] + "-" + ruleDetails[2]);
}
}
default: {
ScreenSize screenSize = ScreenSize.fromString(ruleDetails[0]);
if (ruleDetails[2].endsWith(PIXEL_SUFFIX)) {
offsetRules.put(screenSize,
new AbsoluteOffsetRule(Float.parseFloat(ruleDetails[2].replace(PIXEL_SUFFIX, EMPTY_STRING))));
} else if (ruleDetails[2].endsWith(COLUMN_SUFFIX)) {
if(!horizontalRuleset) {
throw new MdxException("Invalid offset - cannot use column offset for vertical size rules. Must be pixel (px)");
}
offsetRules.put(screenSize,
new ResponsiveOffsetRule(Integer.parseInt(ruleDetails[2].replace(COLUMN_SUFFIX, EMPTY_STRING))));
} else {
throw new MdxException("Invalid offset - must end with c (columns) or px (pixels");
}
}
}
}
private void finaliseRuleset(Map sizeRules, Map offsetRules) {
Iterator screenSizes = ScreenSize.smallestToLargest();
SizeRule lastSizeRule = new ResponsiveSizeRule(12);
OffsetRule lastOffsetRule = new AbsoluteOffsetRule(0);
while (screenSizes.hasNext()) {
ScreenSize nextSize = screenSizes.next();
if (!sizeRules.containsKey(nextSize)) {
sizeRules.put(nextSize, lastSizeRule);
} else {
lastSizeRule = sizeRules.get(nextSize);
}
if (!offsetRules.containsKey(nextSize)) {
offsetRules.put(nextSize, lastOffsetRule);
} else {
lastOffsetRule = offsetRules.get(nextSize);
}
}
}
@Override
public void layout(LayoutState layoutState, ParentRenderNode, ?> parentNode, Array> children) {
flexDirection.layout(layoutState, parentNode, children);
}
@Override
public float getPreferredElementRelativeX(LayoutState layoutState) {
currentOffsetXRule = offsetXRules.get(layoutState.getScreenSize());
return currentOffsetXRule.getOffset(layoutState);
}
@Override
public float getPreferredElementRelativeY(LayoutState layoutState) {
currentOffsetYRule = offsetYRules.get(layoutState.getScreenSize());
return currentOffsetYRule.getOffset(layoutState);
}
@Override
public float getPreferredElementWidth(LayoutState layoutState) {
currentWidthRule = widthRules.get(layoutState.getScreenSize());
return currentWidthRule.getSize(layoutState);
}
@Override
public float getPreferredElementHeight(LayoutState layoutState) {
currentHeightRule = heightRules.get(layoutState.getScreenSize());
return currentHeightRule.getSize(layoutState);
}
public boolean isHiddenByInputSource(LayoutState layoutState) {
switch(layoutState.getLastInputSource()) {
case CONTROLLER:
if(hiddenByGamePadType.isEmpty()) {
hiddenByInputSource = hiddenByInput.contains(layoutState.getLastInputSource());
} else {
hiddenByInputSource = hiddenByGamePadType.contains(layoutState.getLastGamePadType());
}
break;
case KEYBOARD_MOUSE:
hiddenByInputSource = hiddenByInput.contains(layoutState.getLastInputSource());
break;
case TOUCHSCREEN:
hiddenByInputSource = hiddenByInput.contains(layoutState.getLastInputSource());
break;
default:
break;
}
return hiddenByInputSource;
}
public boolean isHiddenByInputSource() {
return hiddenByInputSource;
}
public SizeRule getCurrentWidthRule() {
return currentWidthRule;
}
public SizeRule getCurrentHeightRule() {
return currentHeightRule;
}
public OffsetRule getCurrentOffsetXRule() {
return currentOffsetXRule;
}
public OffsetRule getCurrentOffsetYRule() {
return currentOffsetYRule;
}
@Override
public boolean isFlexLayout() {
return true;
}
public boolean equals(String rules) {
if(rules == null) {
return false;
}
if(rules.isEmpty()) {
return false;
}
return this.rules.equals(rules);
}
public static FlexLayoutRuleset parse(String layout) {
final String [] typeAndValue = layout.toLowerCase().split(":");
switch(typeAndValue[0]) {
case "flex-col":
case "flex-column":
return new FlexLayoutRuleset(FlexDirection.COLUMN, layout, typeAndValue[1]);
case "flex-r":
case "flex-row":
return new FlexLayoutRuleset(FlexDirection.ROW, layout, typeAndValue[1]);
case "flex-col-r":
case "flex-column-r":
case "flex-column-reverse":
return new FlexLayoutRuleset(FlexDirection.COLUMN_REVERSE, layout, typeAndValue[1]);
case "flex-r-r":
case "flex-row-r":
case "flex-row-reverse":
return new FlexLayoutRuleset(FlexDirection.ROW_REVERSE, layout, typeAndValue[1]);
case "flex-cen":
case "flex-centre":
case "flex-center":
return new FlexLayoutRuleset(FlexDirection.CENTER, layout, typeAndValue[1]);
}
throw new MdxException("Invalid layout type '" + typeAndValue[0] + "'");
}
public static String set(String flexLayout, float x, float y, float width, float height) {
flexLayout = setXY(flexLayout, x, y);
flexLayout = setWidth(flexLayout, width);
flexLayout = setHeight(flexLayout, height);
return flexLayout;
}
public static String setXY(String flexLayout, float x, float y) {
flexLayout = setX(flexLayout, x);
flexLayout = setY(flexLayout, y);
return flexLayout;
}
public static String setX(String flexLayout, float x) {
final int valueIndex = flexLayout.indexOf(':') + 1;
final String flexComponent = flexLayout.substring(0, valueIndex);
final String [] xyComponents = flexLayout.substring(valueIndex).split(",");
final String [] xComponents = xyComponents[0].split(" ");
final String [] yComponents = xyComponents.length > 1 ? xyComponents[1].split(" ") : new String[0];
final StringBuilder xResult = new StringBuilder();
final StringBuilder yResult = new StringBuilder();
for(int i = 0; i < xComponents.length; i++) {
if(xComponents[i].contains("offset")) {
continue;
}
xResult.append(xComponents[i]);
xResult.append(' ');
}
xResult.append("xs-offset-");
xResult.append(MathUtils.round(x));
xResult.append("px");
if(yComponents.length == 0) {
yResult.append("xs-auto");
} else {
yResult.append(flexLayout.substring(flexLayout.lastIndexOf(',') + 1));
}
return flexComponent + xResult.toString().trim() + ',' + yResult.toString().trim();
}
public static String setY(String flexLayout, float y) {
final int valueIndex = flexLayout.indexOf(':') + 1;
final String flexComponent = flexLayout.substring(0, valueIndex);
final String [] xyComponents = flexLayout.substring(valueIndex).split(",");
final String [] xComponents = xyComponents[0].split(" ");
final String [] yComponents = xyComponents.length > 1 ? xyComponents[1].split(" ") : new String[0];
final StringBuilder xResult = new StringBuilder();
final StringBuilder yResult = new StringBuilder();
for(int i = 0; i < xComponents.length; i++) {
xResult.append(xComponents[i]);
xResult.append(' ');
}
if(yComponents.length > 0) {
for(int i = 0; i < yComponents.length; i++) {
if(yComponents[i].contains("offset")) {
continue;
}
yResult.append(yComponents[i]);
yResult.append(' ');
}
} else {
yResult.append("xs-auto");
yResult.append(' ');
}
yResult.append("xs-offset-");
yResult.append(MathUtils.round(y));
yResult.append("px");
return flexComponent + xResult.toString().trim() + ',' + yResult.toString().trim();
}
public static String setWidth(String flexLayout, float width) {
final int valueIndex = flexLayout.indexOf(':') + 1;
final String flexComponent = flexLayout.substring(0, valueIndex);
final String [] xyComponents = flexLayout.substring(valueIndex).split(",");
final String [] xComponents = xyComponents[0].split(" ");
final String [] yComponents = xyComponents.length > 1 ? xyComponents[1].split(" ") : new String[0];
final StringBuilder xResult = new StringBuilder();
final StringBuilder yResult = new StringBuilder();
for(int i = 0; i < xComponents.length; i++) {
if(!xComponents[i].contains("offset")) {
continue;
}
xResult.append(xComponents[i]);
xResult.append(' ');
}
xResult.append("xs-");
xResult.append(MathUtils.round(width));
xResult.append("px");
if(yComponents.length == 0) {
yResult.append("xs-auto");
} else {
yResult.append(flexLayout.substring(flexLayout.lastIndexOf(',') + 1));
}
return flexComponent + xResult.toString().trim() + ',' + yResult.toString().trim();
}
public static String setHeight(String flexLayout, float height) {
final int valueIndex = flexLayout.indexOf(':') + 1;
final String flexComponent = flexLayout.substring(0, valueIndex);
final String [] xyComponents = flexLayout.substring(valueIndex).split(",");
final String [] xComponents = xyComponents[0].split(" ");
final String [] yComponents = xyComponents.length > 1 ? xyComponents[1].split(" ") : new String[0];
final StringBuilder xResult = new StringBuilder();
final StringBuilder yResult = new StringBuilder();
for(int i = 0; i < xComponents.length; i++) {
xResult.append(xComponents[i]);
xResult.append(' ');
}
if(yComponents.length > 0) {
for(int i = 0; i < yComponents.length; i++) {
if(!yComponents[i].contains("offset")) {
continue;
}
yResult.append(yComponents[i]);
yResult.append(' ');
}
}
yResult.append("xs-");
yResult.append(MathUtils.round(height));
yResult.append("px");
return flexComponent + xResult.toString().trim() + ',' + yResult.toString().trim();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy