compile group: 'androidx.constraintlayout', name: 'constraintlayout-core', version: '1.1.0-beta01'
Artifact androidx.constraintlayout:constraintlayout-core:1.1.0-beta01 it located at Google repository (https://maven.google.com/)
Parse and build a motionScene
this should be in a MotionScene / MotionSceneParser
Top leve parsing of the json ConstraintSet supporting
"Variables", "Helpers", "Generate", guidelines, and barriers
parse the Design time elements.
/*
* Copyright (C) 2022 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.
*/
package androidx.constraintlayout.core.state;
import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_INTERPOLATOR_TYPE;
import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_MOTIONSTEPS;
import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_MOTION_PHASE;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.constraintlayout.core.motion.utils.TypedBundle;
import androidx.constraintlayout.core.motion.utils.TypedValues;
import androidx.constraintlayout.core.parser.CLArray;
import androidx.constraintlayout.core.parser.CLElement;
import androidx.constraintlayout.core.parser.CLKey;
import androidx.constraintlayout.core.parser.CLNumber;
import androidx.constraintlayout.core.parser.CLObject;
import androidx.constraintlayout.core.parser.CLParser;
import androidx.constraintlayout.core.parser.CLParsingException;
import androidx.constraintlayout.core.parser.CLString;
import androidx.constraintlayout.core.state.helpers.BarrierReference;
import androidx.constraintlayout.core.state.helpers.ChainReference;
import androidx.constraintlayout.core.state.helpers.FlowReference;
import androidx.constraintlayout.core.state.helpers.GridReference;
import androidx.constraintlayout.core.state.helpers.GuidelineReference;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.Flow;
import java.util.ArrayList;
import java.util.HashMap;
public class ConstraintSetParser {
private static final boolean PARSER_DEBUG = false;
public static class DesignElement {
String mId;
String mType;
HashMap<String, String> mParams;
public String getId() {
return mId;
}
public String getType() {
return mType;
}
public HashMap<String, String> getParams() {
return mParams;
}
DesignElement(String id,
String type,
HashMap<String, String> params) {
mId = id;
mType = type;
mParams = params;
}
}
/**
* Provide the storage for managing Variables in the system.
* When the json has a variable:{ } section this is used.
*/
public static class LayoutVariables {
HashMap<String, Integer> mMargins = new HashMap<>();
HashMap<String, GeneratedValue> mGenerators = new HashMap<>();
HashMap<String, ArrayList<String>> mArrayIds = new HashMap<>();
void put(String elementName, int element) {
mMargins.put(elementName, element);
}
void put(String elementName, float start, float incrementBy) {
if (mGenerators.containsKey(elementName)) {
if (mGenerators.get(elementName) instanceof OverrideValue) {
return;
}
}
mGenerators.put(elementName, new Generator(start, incrementBy));
}
void put(String elementName,
float from,
float to,
float step,
String prefix,
String postfix) {
if (mGenerators.containsKey(elementName)) {
if (mGenerators.get(elementName) instanceof OverrideValue) {
return;
}
}
FiniteGenerator generator =
new FiniteGenerator(from, to, step, prefix, postfix);
mGenerators.put(elementName, generator);
mArrayIds.put(elementName, generator.array());
}
/**
* insert an override variable
*
* @param elementName the name
* @param value the value a float
*/
public void putOverride(String elementName, float value) {
GeneratedValue generator = new OverrideValue(value);
mGenerators.put(elementName, generator);
}
float get(Object elementName) {
if (elementName instanceof CLString) {
String stringValue = ((CLString) elementName).content();
if (mGenerators.containsKey(stringValue)) {
return mGenerators.get(stringValue).value();
}
if (mMargins.containsKey(stringValue)) {
return mMargins.get(stringValue).floatValue();
}
} else if (elementName instanceof CLNumber) {
return ((CLNumber) elementName).getFloat();
}
return 0f;
}
ArrayList<String> getList(String elementName) {
if (mArrayIds.containsKey(elementName)) {
return mArrayIds.get(elementName);
}
return null;
}
void put(String elementName, ArrayList<String> elements) {
mArrayIds.put(elementName, elements);
}
}
interface GeneratedValue {
float value();
}
/**
* Generate a floating point value
*/
static class Generator implements GeneratedValue {
float mStart = 0;
float mIncrementBy = 0;
float mCurrent = 0;
boolean mStop = false;
Generator(float start, float incrementBy) {
mStart = start;
mIncrementBy = incrementBy;
mCurrent = start;
}
@Override
public float value() {
if (!mStop) {
mCurrent += mIncrementBy;
}
return mCurrent;
}
}
/**
* Generate values like button1, button2 etc.
*/
static class FiniteGenerator implements GeneratedValue {
float mFrom = 0;
float mTo = 0;
float mStep = 0;
boolean mStop = false;
String mPrefix;
String mPostfix;
float mCurrent = 0;
float mInitial;
float mMax;
FiniteGenerator(float from,
float to,
float step,
String prefix,
String postfix) {
mFrom = from;
mTo = to;
mStep = step;
mPrefix = (prefix == null) ? "" : prefix;
mPostfix = (postfix == null) ? "" : postfix;
mMax = to;
mInitial = from;
}
@Override
public float value() {
if (mCurrent >= mMax) {
mStop = true;
}
if (!mStop) {
mCurrent += mStep;
}
return mCurrent;
}
public ArrayList<String> array() {
ArrayList<String> array = new ArrayList<>();
int value = (int) mInitial;
int maxInt = (int) mMax;
for (int i = value; i <= maxInt; i++) {
array.add(mPrefix + value + mPostfix);
value += (int) mStep;
}
return array;
}
}
static class OverrideValue implements GeneratedValue {
float mValue;
OverrideValue(float value) {
mValue = value;
}
@Override
public float value() {
return mValue;
}
}
//==================== end store variables =========================
//==================== MotionScene =========================
public enum MotionLayoutDebugFlags {
NONE,
SHOW_ALL,
UNKNOWN
}
//==================== end Motion Scene =========================
/**
* Parse and populate a transition
*
* @param content JSON string to parse
* @param transition The Transition to be populated
* @param state
*/
public static void parseJSON(String content, Transition transition, int state) {
try {
CLObject json = CLParser.parse(content);
ArrayList<String> elements = json.names();
if (elements == null) {
return;
}
for (String elementName : elements) {
CLElement base_element = json.get(elementName);
if (base_element instanceof CLObject) {
CLObject element = (CLObject) base_element;
CLObject customProperties = element.getObjectOrNull("custom");
if (customProperties != null) {
ArrayList<String> properties = customProperties.names();
for (String property : properties) {
CLElement value = customProperties.get(property);
if (value instanceof CLNumber) {
transition.addCustomFloat(
state,
elementName,
property,
value.getFloat()
);
} else if (value instanceof CLString) {
long color = parseColorString(value.content());
if (color != -1) {
transition.addCustomColor(state,
elementName, property, (int) color);
}
}
}
}
}
}
} catch (CLParsingException e) {
System.err.println("Error parsing JSON " + e);
}
}
/**
* Parse and build a motionScene
*
* this should be in a MotionScene / MotionSceneParser
*/
public static void parseMotionSceneJSON(CoreMotionScene scene, String content) {
try {
CLObject json = CLParser.parse(content);
ArrayList<String> elements = json.names();
if (elements == null) {
return;
}
for (String elementName : elements) {
CLElement element = json.get(elementName);
if (element instanceof CLObject) {
CLObject clObject = (CLObject) element;
switch (elementName) {
case "ConstraintSets":
parseConstraintSets(scene, clObject);
break;
case "Transitions":
parseTransitions(scene, clObject);
break;
case "Header":
parseHeader(scene, clObject);
break;
}
}
}
} catch (CLParsingException e) {
System.err.println("Error parsing JSON " + e);
}
}
/**
* Parse ConstraintSets and populate MotionScene
*/
static void parseConstraintSets(CoreMotionScene scene,
CLObject json) throws CLParsingException {
ArrayList<String> constraintSetNames = json.names();
if (constraintSetNames == null) {
return;
}
for (String csName : constraintSetNames) {
CLObject constraintSet = json.getObject(csName);
boolean added = false;
String ext = constraintSet.getStringOrNull("Extends");
if (ext != null && !ext.isEmpty()) {
String base = scene.getConstraintSet(ext);
if (base == null) {
continue;
}
CLObject baseJson = CLParser.parse(base);
ArrayList<String> widgetsOverride = constraintSet.names();
if (widgetsOverride == null) {
continue;
}
for (String widgetOverrideName : widgetsOverride) {
CLElement value = constraintSet.get(widgetOverrideName);
if (value instanceof CLObject) {
override(baseJson, widgetOverrideName, (CLObject) value);
}
}
scene.setConstraintSetContent(csName, baseJson.toJSON());
added = true;
}
if (!added) {
scene.setConstraintSetContent(csName, constraintSet.toJSON());
}
}
}
static void override(CLObject baseJson,
String name, CLObject overrideValue) throws CLParsingException {
if (!baseJson.has(name)) {
baseJson.put(name, overrideValue);
} else {
CLObject base = baseJson.getObject(name);
ArrayList<String> keys = overrideValue.names();
for (String key : keys) {
if (!key.equals("clear")) {
base.put(key, overrideValue.get(key));
continue;
}
CLArray toClear = overrideValue.getArray("clear");
for (int i = 0; i < toClear.size(); i++) {
String clearedKey = toClear.getStringOrNull(i);
if (clearedKey == null) {
continue;
}
switch (clearedKey) {
case "dimensions":
base.remove("width");
base.remove("height");
break;
case "constraints":
base.remove("start");
base.remove("end");
base.remove("top");
base.remove("bottom");
base.remove("baseline");
base.remove("center");
base.remove("centerHorizontally");
base.remove("centerVertically");
break;
case "transforms":
base.remove("visibility");
base.remove("alpha");
base.remove("pivotX");
base.remove("pivotY");
base.remove("rotationX");
base.remove("rotationY");
base.remove("rotationZ");
base.remove("scaleX");
base.remove("scaleY");
base.remove("translationX");
base.remove("translationY");
break;
default:
base.remove(clearedKey);
}
}
}
}
}
/**
* Parse the Transition
*/
static void parseTransitions(CoreMotionScene scene, CLObject json) throws CLParsingException {
ArrayList<String> elements = json.names();
if (elements == null) {
return;
}
for (String elementName : elements) {
scene.setTransitionContent(elementName, json.getObject(elementName).toJSON());
}
}
/**
* Used to parse for "export"
*/
static void parseHeader(CoreMotionScene scene, CLObject json) {
String name = json.getStringOrNull("export");
if (name != null) {
scene.setDebugName(name);
}
}
/**
* Top leve parsing of the json ConstraintSet supporting
* "Variables", "Helpers", "Generate", guidelines, and barriers
*
* @param content the JSON string
* @param state the state to populate
* @param layoutVariables the variables to override
*/
public static void parseJSON(String content, State state,
LayoutVariables layoutVariables) throws CLParsingException {
try {
CLObject json = CLParser.parse(content);
populateState(json, state, layoutVariables);
} catch (CLParsingException e) {
System.err.println("Error parsing JSON " + e);
}
}
/**
* Populates the given {@link State} with the parameters from {@link CLObject}. Where the
* object represents a parsed JSONObject of a ConstraintSet.
*
* @param parsedJson CLObject of the parsed ConstraintSet
* @param state the state to populate
* @param layoutVariables the variables to override
* @throws CLParsingException when parsing fails
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static void populateState(
@NonNull CLObject parsedJson,
@NonNull State state,
@NonNull LayoutVariables layoutVariables
) throws CLParsingException {
ArrayList<String> elements = parsedJson.names();
if (elements == null) {
return;
}
for (String elementName : elements) {
CLElement element = parsedJson.get(elementName);
if (PARSER_DEBUG) {
System.out.println("[" + elementName + "] = " + element
+ " > " + element.getContainer());
}
switch (elementName) {
case "Variables":
if (element instanceof CLObject) {
parseVariables(state, layoutVariables, (CLObject) element);
}
break;
case "Helpers":
if (element instanceof CLArray) {
parseHelpers(state, layoutVariables, (CLArray) element);
}
break;
case "Generate":
if (element instanceof CLObject) {
parseGenerate(state, layoutVariables, (CLObject) element);
}
break;
default:
if (element instanceof CLObject) {
String type = lookForType((CLObject) element);
if (type != null) {
switch (type) {
case "hGuideline":
parseGuidelineParams(
ConstraintWidget.HORIZONTAL,
state,
elementName,
(CLObject) element
);
break;
case "vGuideline":
parseGuidelineParams(
ConstraintWidget.VERTICAL,
state,
elementName,
(CLObject) element
);
break;
case "barrier":
parseBarrier(state, elementName, (CLObject) element);
break;
case "vChain":
case "hChain":
parseChainType(
type,
state,
elementName,
layoutVariables,
(CLObject) element
);
break;
case "vFlow":
case "hFlow":
parseFlowType(
type,
state,
elementName,
layoutVariables,
(CLObject) element
);
break;
case "grid":
case "row":
case "column":
parseGridType(
type,
state,
elementName,
layoutVariables,
(CLObject) element
);
break;
}
} else {
parseWidget(state,
layoutVariables,
elementName,
(CLObject) element);
}
} else if (element instanceof CLNumber) {
layoutVariables.put(elementName, element.getInt());
}
}
}
}
private static void parseVariables(State state,
LayoutVariables layoutVariables,
CLObject json) throws CLParsingException {
ArrayList<String> elements = json.names();
if (elements == null) {
return;
}
for (String elementName : elements) {
CLElement element = json.get(elementName);
if (element instanceof CLNumber) {
layoutVariables.put(elementName, element.getInt());
} else if (element instanceof CLObject) {
CLObject obj = (CLObject) element;
ArrayList<String> arrayIds;
if (obj.has("from") && obj.has("to")) {
float from = layoutVariables.get(obj.get("from"));
float to = layoutVariables.get(obj.get("to"));
String prefix = obj.getStringOrNull("prefix");
String postfix = obj.getStringOrNull("postfix");
layoutVariables.put(elementName, from, to, 1f, prefix, postfix);
} else if (obj.has("from") && obj.has("step")) {
float start = layoutVariables.get(obj.get("from"));
float increment = layoutVariables.get(obj.get("step"));
layoutVariables.put(elementName, start, increment);
} else if (obj.has("ids")) {
CLArray ids = obj.getArray("ids");
arrayIds = new ArrayList<>();
for (int i = 0; i < ids.size(); i++) {
arrayIds.add(ids.getString(i));
}
layoutVariables.put(elementName, arrayIds);
} else if (obj.has("tag")) {
arrayIds = state.getIdsForTag(obj.getString("tag"));
layoutVariables.put(elementName, arrayIds);
}
}
}
}
/**
* parse the Design time elements.
*
* @param content the json
* @param list output the list of design elements
*/
public static void parseDesignElementsJSON(
String content, ArrayList<DesignElement> list) throws CLParsingException {
CLObject json = CLParser.parse(content);
ArrayList<String> elements = json.names();
if (elements == null) {
return;
}
for (int i = 0; i < elements.size(); i++) {
String elementName = elements.get(i);
CLElement element = json.get(elementName);
if (PARSER_DEBUG) {
System.out.println("[" + element + "] " + element.getClass());
}
switch (elementName) {
case "Design":
if (!(element instanceof CLObject)) {
return;
}
CLObject obj = (CLObject) element;
elements = obj.names();
for (int j = 0; j < elements.size(); j++) {
String designElementName = elements.get(j);
CLObject designElement =
(CLObject) ((CLObject) element).get(designElementName);
System.out.printf("element found " + designElementName + "");
String type = designElement.getStringOrNull("type");
if (type != null) {
HashMap<String, String> parameters = new HashMap<String, String>();
int size = designElement.size();
for (int k = 0; k < size; k++) {
CLKey key = (CLKey) designElement.get(j);
String paramName = key.content();
String paramValue = key.getValue().content();
if (paramValue != null) {
parameters.put(paramName, paramValue);
}
}
list.add(new DesignElement(elementName, type, parameters));
}
}
}
break;
}
}
static void parseHelpers(State state,
LayoutVariables layoutVariables,
CLArray element) throws CLParsingException {
for (int i = 0; i < element.size(); i++) {
CLElement helper = element.get(i);
if (helper instanceof CLArray) {
CLArray array = (CLArray) helper;
if (array.size() > 1) {
switch (array.getString(0)) {
case "hChain":
parseChain(ConstraintWidget.HORIZONTAL, state, layoutVariables, array);
break;
case "vChain":
parseChain(ConstraintWidget.VERTICAL, state, layoutVariables, array);
break;
case "hGuideline":
parseGuideline(ConstraintWidget.HORIZONTAL, state, array);
break;
case "vGuideline":
parseGuideline(ConstraintWidget.VERTICAL, state, array);
break;
}
}
}
}
}
static void parseGenerate(State state,
LayoutVariables layoutVariables,
CLObject json) throws CLParsingException {
ArrayList<String> elements = json.names();
if (elements == null) {
return;
}
for (String elementName : elements) {
CLElement element = json.get(elementName);
ArrayList<String> arrayIds = layoutVariables.getList(elementName);
if (arrayIds != null && element instanceof CLObject) {
for (String id : arrayIds) {
parseWidget(state, layoutVariables, id, (CLObject) element);
}
}
}
}
static void parseChain(int orientation, State state,
LayoutVariables margins, CLArray helper) throws CLParsingException {
ChainReference chain = (orientation == ConstraintWidget.HORIZONTAL)
? state.horizontalChain() : state.verticalChain();
CLElement refs = helper.get(1);
if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) {
return;
}
for (int i = 0; i < ((CLArray) refs).size(); i++) {
chain.add(((CLArray) refs).getString(i));
}
if (helper.size() > 2) { // we have additional parameters
CLElement params = helper.get(2);
if (!(params instanceof CLObject)) {
return;
}
CLObject obj = (CLObject) params;
ArrayList<String> constraints = obj.names();
for (String constraintName : constraints) {
switch (constraintName) {
case "style":
CLElement styleObject = ((CLObject) params).get(constraintName);
String styleValue;
if (styleObject instanceof CLArray && ((CLArray) styleObject).size() > 1) {
styleValue = ((CLArray) styleObject).getString(0);
float biasValue = ((CLArray) styleObject).getFloat(1);
chain.bias(biasValue);
} else {
styleValue = styleObject.content();
}
switch (styleValue) {
case "packed":
chain.style(State.Chain.PACKED);
break;
case "spread_inside":
chain.style(State.Chain.SPREAD_INSIDE);
break;
default:
chain.style(State.Chain.SPREAD);
break;
}
break;
default:
parseConstraint(
state,
margins,
(CLObject) params,
(ConstraintReference) chain,
constraintName
);
break;
}
}
}
}
private static float toPix(State state, float dp) {
return state.getDpToPixel().toPixels(dp);
}
/**
* Support parsing Chain in the following manner
* chainId : {
* type:'hChain' // or vChain
* contains: ['id1', 'id2', 'id3' ]
* contains: [['id', weight, marginL ,marginR], 'id2', 'id3' ]
* start: ['parent', 'start',0],
* end: ['parent', 'end',0],
* top: ['parent', 'top',0],
* bottom: ['parent', 'bottom',0],
* style: 'spread'
* }
* @throws CLParsingException
*/
private static void parseChainType(String orientation,
State state,
String chainName,
LayoutVariables margins,
CLObject object) throws CLParsingException {
ChainReference chain = (orientation.charAt(0) == 'h')
? state.horizontalChain() : state.verticalChain();
chain.setKey(chainName);
for (String params : object.names()) {
switch (params) {
case "contains":
CLElement refs = object.get(params);
if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) {
System.err.println(
chainName + " contains should be an array \"" + refs.content()
+ "\"");
return;
}
for (int i = 0; i < ((CLArray) refs).size(); i++) {
CLElement chainElement = ((CLArray) refs).get(i);
if (chainElement instanceof CLArray) {
CLArray array = (CLArray) chainElement;
if (array.size() > 0) {
String id = array.get(0).content();
float weight = Float.NaN;
float preMargin = Float.NaN;
float postMargin = Float.NaN;
float preGoneMargin = Float.NaN;
float postGoneMargin = Float.NaN;
switch (array.size()) {
case 2: // sets only the weight
weight = array.getFloat(1);
break;
case 3: // sets the pre and post margin to the 2 arg
weight = array.getFloat(1);
postMargin = preMargin = toPix(state, array.getFloat(2));
break;
case 4: // sets the pre and post margin
weight = array.getFloat(1);
preMargin = toPix(state, array.getFloat(2));
postMargin = toPix(state, array.getFloat(3));
break;
case 6: // weight, preMargin, postMargin, preGoneMargin,
// postGoneMargin
weight = array.getFloat(1);
preMargin = toPix(state, array.getFloat(2));
postMargin = toPix(state, array.getFloat(3));
preGoneMargin = toPix(state, array.getFloat(4));
postGoneMargin = toPix(state, array.getFloat(5));
break;
}
chain.addChainElement(id,
weight,
preMargin,
postMargin,
preGoneMargin,
postGoneMargin);
}
} else {
chain.add(chainElement.content());
}
}
break;
case "start":
case "end":
case "top":
case "bottom":
case "left":
case "right":
parseConstraint(state, margins, object, chain, params);
break;
case "style":
CLElement styleObject = object.get(params);
String styleValue;
if (styleObject instanceof CLArray && ((CLArray) styleObject).size() > 1) {
styleValue = ((CLArray) styleObject).getString(0);
float biasValue = ((CLArray) styleObject).getFloat(1);
chain.bias(biasValue);
} else {
styleValue = styleObject.content();
}
switch (styleValue) {
case "packed":
chain.style(State.Chain.PACKED);
break;
case "spread_inside":
chain.style(State.Chain.SPREAD_INSIDE);
break;
default:
chain.style(State.Chain.SPREAD);
break;
}
break;
}
}
}
/**
* Support parsing Grid in the following manner
* chainId : {
* height: "parent",
* width: "parent",
* type: "Grid",
* vGap: 10,
* hGap: 10,
* orientation: 0,
* rows: 0,
* columns: 1,
* columnWeights: "",
* rowWeights: "",
* contains: ["btn1", "btn2", "btn3", "btn4"],
* top: ["parent", "top", 10],
* bottom: ["parent", "bottom", 20],
* right: ["parent", "right", 30],
* left: ["parent", "left", 40],
* }
*
* @param gridType type of the Grid helper could be "Grid"|"Row"|"Column"
* @param state ConstraintLayout State
* @param name the name of the Grid Helper
* @param layoutVariables layout margins
* @param element the element to be parsed
* @throws CLParsingException
*/
private static void parseGridType(String gridType,
State state,
String name,
LayoutVariables layoutVariables,
CLObject element) throws CLParsingException {
GridReference grid = state.getGrid(name, gridType);
for (String param : element.names()) {
switch (param) {
case "contains":
CLArray list = element.getArrayOrNull(param);
if (list != null) {
for (int j = 0; j < list.size(); j++) {
String elementNameReference = list.get(j).content();
ConstraintReference elementReference =
state.constraints(elementNameReference);
grid.add(elementReference);
}
}
break;
case "orientation":
int orientation = element.get(param).getInt();
grid.setOrientation(orientation);
break;
case "rows":
int rows = element.get(param).getInt();
if (rows > 0) {
grid.setRowsSet(rows);
}
break;
case "columns":
int columns = element.get(param).getInt();
if (columns > 0) {
grid.setColumnsSet(columns);
}
break;
case "hGap":
float hGap = element.get(param).getFloat();
grid.setHorizontalGaps(toPix(state, hGap));
break;
case "vGap":
float vGap = element.get(param).getFloat();
grid.setVerticalGaps(toPix(state, vGap));
break;
case "spans":
String spans = element.get(param).content();
if (spans != null && spans.contains(":")) {
grid.setSpans(spans);
}
break;
case "skips":
String skips = element.get(param).content();
if (skips != null && skips.contains(":")) {
grid.setSkips(skips);
}
break;
case "rowWeights":
String rowWeights = element.get(param).content();
if (rowWeights != null && rowWeights.contains(",")) {
grid.setRowWeights(rowWeights);
}
break;
case "columnWeights":
String columnWeights = element.get(param).content();
if (columnWeights != null && columnWeights.contains(",")) {
grid.setColumnWeights(columnWeights);
}
break;
case "padding":
// Note that padding is currently not properly handled in GridCore
CLElement paddingObject = element.get(param);
float paddingStart = 0;
float paddingTop = 0;
float paddingEnd = 0;
float paddingBottom = 0;
if (paddingObject instanceof CLArray && ((CLArray) paddingObject).size() > 1) {
paddingStart = ((CLArray) paddingObject).getInt(0);
paddingEnd = paddingStart;
paddingTop = ((CLArray) paddingObject).getInt(1);
paddingBottom = paddingTop;
if (((CLArray) paddingObject).size() > 2) {
paddingEnd = ((CLArray) paddingObject).getInt(2);
try {
paddingBottom = ((CLArray) paddingObject).getInt(3);
} catch (ArrayIndexOutOfBoundsException e) {
paddingBottom = 0;
}
}
} else {
paddingStart = paddingObject.getInt();
paddingTop = paddingStart;
paddingEnd = paddingStart;
paddingBottom = paddingStart;
}
grid.setPaddingStart(Math.round(toPix(state, paddingStart)));
grid.setPaddingTop(Math.round(toPix(state, paddingTop)));
grid.setPaddingEnd(Math.round(toPix(state, paddingEnd)));
grid.setPaddingBottom(Math.round(toPix(state, paddingBottom)));
break;
case "flags":
int flagValue = 0;
String flags = "";
try {
CLElement obj = element.get(param);
if (obj instanceof CLNumber) {
flagValue = obj.getInt();
} else {
flags = obj.content();
}
} catch (Exception ex) {
System.err.println("Error parsing grid flags " + ex);
}
if (flags != null && !flags.isEmpty()) {
// In older APIs, the flags may still be defined as a String
grid.setFlags(flags);
} else {
grid.setFlags(flagValue);
}
break;
default:
ConstraintReference reference = state.constraints(name);
applyAttribute(state, layoutVariables, reference, element, param);
}
}
}
/**
* It's used to parse the Flow type of Helper with the following format:
* flowID: {
* type: 'hFlow'|'vFlow’
* wrap: 'chain'|'none'|'aligned',
* contains: ['id1', 'id2', 'id3' ] |
* [['id1', weight, preMargin , postMargin], 'id2', 'id3'],
* vStyle: 'spread'|'spread_inside'|'packed' | ['first', 'middle', 'last'],
* hStyle: 'spread'|'spread_inside'|'packed' | ['first', 'middle', 'last'],
* vAlign: 'top'|'bottom'|'baseline'|'center',
* hAlign: 'start'|'end'|'center',
* vGap: 32,
* hGap: 23,
* padding: 32,
* maxElement: 5,
* vBias: 0.3 | [0.0, 0.5, 0.5],
* hBias: 0.4 | [0.0, 0.5, 0.5],
* start: ['parent', 'start', 0],
* end: ['parent', 'end', 0],
* top: ['parent', 'top', 0],
* bottom: ['parent', 'bottom', 0],
* }
*
* @param flowType orientation of the Flow Helper
* @param state ConstraintLayout State
* @param flowName the name of the Flow Helper
* @param layoutVariables layout margins
* @param element the element to be parsed
* @throws CLParsingException
*/
private static void parseFlowType(String flowType,
State state,
String flowName,
LayoutVariables layoutVariables,
CLObject element) throws CLParsingException {
boolean isVertical = flowType.charAt(0) == 'v';
FlowReference flow = state.getFlow(flowName, isVertical);
for (String param : element.names()) {
switch (param) {
case "contains":
CLElement refs = element.get(param);
if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) {
System.err.println(
flowName + " contains should be an array \"" + refs.content()
+ "\"");
return;
}
for (int i = 0; i < ((CLArray) refs).size(); i++) {
CLElement chainElement = ((CLArray) refs).get(i);
if (chainElement instanceof CLArray) {
CLArray array = (CLArray) chainElement;
if (array.size() > 0) {
String id = array.get(0).content();
float weight = Float.NaN;
float preMargin = Float.NaN;
float postMargin = Float.NaN;
switch (array.size()) {
case 2: // sets only the weight
weight = array.getFloat(1);
break;
case 3: // sets the pre and post margin to the 2 arg
weight = array.getFloat(1);
postMargin = preMargin = toPix(state, array.getFloat(2));
break;
case 4: // sets the pre and post margin
weight = array.getFloat(1);
preMargin = toPix(state, array.getFloat(2));
postMargin = toPix(state, array.getFloat(3));
break;
}
flow.addFlowElement(id, weight, preMargin, postMargin);
}
} else {
flow.add(chainElement.content());
}
}
break;
case "type":
if (element.get(param).content().equals("hFlow")) {
flow.setOrientation(HORIZONTAL);
} else {
flow.setOrientation(VERTICAL);
}
break;
case "wrap":
String wrapValue = element.get(param).content();
flow.setWrapMode(State.Wrap.getValueByString(wrapValue));
break;
case "vGap":
int vGapValue = element.get(param).getInt();
flow.setVerticalGap(vGapValue);
break;
case "hGap":
int hGapValue = element.get(param).getInt();
flow.setHorizontalGap(hGapValue);
break;
case "maxElement":
int maxElementValue = element.get(param).getInt();
flow.setMaxElementsWrap(maxElementValue);
break;
case "padding":
CLElement paddingObject = element.get(param);
float paddingLeft = 0;
float paddingTop = 0;
float paddingRight = 0;
float paddingBottom = 0;
if (paddingObject instanceof CLArray && ((CLArray) paddingObject).size() > 1) {
paddingLeft = ((CLArray) paddingObject).getInt(0);
paddingRight = paddingLeft;
paddingTop = ((CLArray) paddingObject).getInt(1);
paddingBottom = paddingTop;
if (((CLArray) paddingObject).size() > 2) {
paddingRight = ((CLArray) paddingObject).getInt(2);
try {
paddingBottom = ((CLArray) paddingObject).getInt(3);
} catch (ArrayIndexOutOfBoundsException e) {
paddingBottom = 0;
}
}
} else {
paddingLeft = paddingObject.getInt();
paddingTop = paddingLeft;
paddingRight = paddingLeft;
paddingBottom = paddingLeft;
}
flow.setPaddingLeft(Math.round(toPix(state, paddingLeft)));
flow.setPaddingTop(Math.round(toPix(state, paddingTop)));
flow.setPaddingRight(Math.round(toPix(state, paddingRight)));
flow.setPaddingBottom(Math.round(toPix(state, paddingBottom)));
break;
case "vAlign":
String vAlignValue = element.get(param).content();
switch (vAlignValue) {
case "top":
flow.setVerticalAlign(Flow.VERTICAL_ALIGN_TOP);
break;
case "bottom":
flow.setVerticalAlign(Flow.VERTICAL_ALIGN_BOTTOM);
break;
case "baseline":
flow.setVerticalAlign(Flow.VERTICAL_ALIGN_BASELINE);
break;
default:
flow.setVerticalAlign(Flow.VERTICAL_ALIGN_CENTER);
break;
}
break;
case "hAlign":
String hAlignValue = element.get(param).content();
switch (hAlignValue) {
case "start":
flow.setHorizontalAlign(Flow.HORIZONTAL_ALIGN_START);
break;
case "end":
flow.setHorizontalAlign(Flow.HORIZONTAL_ALIGN_END);
break;
default:
flow.setHorizontalAlign(Flow.HORIZONTAL_ALIGN_CENTER);
break;
}
break;
case "vFlowBias":
CLElement vBiasObject = element.get(param);
Float vBiasValue = 0.5f;
Float vFirstBiasValue = 0.5f;
Float vLastBiasValue = 0.5f;
if (vBiasObject instanceof CLArray && ((CLArray) vBiasObject).size() > 1) {
vFirstBiasValue = ((CLArray) vBiasObject).getFloat(0);
vBiasValue = ((CLArray) vBiasObject).getFloat(1);
if (((CLArray) vBiasObject).size() > 2) {
vLastBiasValue = ((CLArray) vBiasObject).getFloat(2);
}
} else {
vBiasValue = vBiasObject.getFloat();
}
try {
flow.verticalBias(vBiasValue);
if (vFirstBiasValue != 0.5f) {
flow.setFirstVerticalBias(vFirstBiasValue);
}
if (vLastBiasValue != 0.5f) {
flow.setLastVerticalBias(vLastBiasValue);
}
} catch(NumberFormatException e) {
}
break;
case "hFlowBias":
CLElement hBiasObject = element.get(param);
Float hBiasValue = 0.5f;
Float hFirstBiasValue = 0.5f;
Float hLastBiasValue = 0.5f;
if (hBiasObject instanceof CLArray && ((CLArray) hBiasObject).size() > 1) {
hFirstBiasValue = ((CLArray) hBiasObject).getFloat(0);
hBiasValue = ((CLArray) hBiasObject).getFloat(1);
if (((CLArray) hBiasObject).size() > 2) {
hLastBiasValue = ((CLArray) hBiasObject).getFloat(2);
}
} else {
hBiasValue = hBiasObject.getFloat();
}
try {
flow.horizontalBias(hBiasValue);
if (hFirstBiasValue != 0.5f) {
flow.setFirstHorizontalBias(hFirstBiasValue);
}
if (hLastBiasValue != 0.5f) {
flow.setLastHorizontalBias(hLastBiasValue);
}
} catch(NumberFormatException e) {
}
break;
case "vStyle":
CLElement vStyleObject = element.get(param);
String vStyleValueStr = "";
String vFirstStyleValueStr = "";
String vLastStyleValueStr = "";
if (vStyleObject instanceof CLArray && ((CLArray) vStyleObject).size() > 1) {
vFirstStyleValueStr = ((CLArray) vStyleObject).getString(0);
vStyleValueStr = ((CLArray) vStyleObject).getString(1);
if (((CLArray) vStyleObject).size() > 2) {
vLastStyleValueStr = ((CLArray) vStyleObject).getString(2);
}
} else {
vStyleValueStr = vStyleObject.content();
}
if (!vStyleValueStr.equals("")) {
flow.setVerticalStyle(State.Chain.getValueByString(vStyleValueStr));
}
if (!vFirstStyleValueStr.equals("")) {
flow.setFirstVerticalStyle(
State.Chain.getValueByString(vFirstStyleValueStr));
}
if (!vLastStyleValueStr.equals("")) {
flow.setLastVerticalStyle(State.Chain.getValueByString(vLastStyleValueStr));
}
break;
case "hStyle":
CLElement hStyleObject = element.get(param);
String hStyleValueStr = "";
String hFirstStyleValueStr = "";
String hLastStyleValueStr = "";
if (hStyleObject instanceof CLArray && ((CLArray) hStyleObject).size() > 1) {
hFirstStyleValueStr = ((CLArray) hStyleObject).getString(0);
hStyleValueStr = ((CLArray) hStyleObject).getString(1);
if (((CLArray) hStyleObject).size() > 2) {
hLastStyleValueStr = ((CLArray) hStyleObject).getString(2);
}
} else {
hStyleValueStr = hStyleObject.content();
}
if (!hStyleValueStr.equals("")) {
flow.setHorizontalStyle(State.Chain.getValueByString(hStyleValueStr));
}
if (!hFirstStyleValueStr.equals("")) {
flow.setFirstHorizontalStyle(
State.Chain.getValueByString(hFirstStyleValueStr));
}
if (!hLastStyleValueStr.equals("")) {
flow.setLastHorizontalStyle(
State.Chain.getValueByString(hLastStyleValueStr));
}
break;
default:
// Get the underlying reference for the flow, apply the constraints
// attributes to it
ConstraintReference reference = state.constraints(flowName);
applyAttribute(state, layoutVariables, reference, element, param);
}
}
}
static void parseGuideline(int orientation,
State state, CLArray helper) throws CLParsingException {
CLElement params = helper.get(1);
if (!(params instanceof CLObject)) {
return;
}
String guidelineId = ((CLObject) params).getStringOrNull("id");
if (guidelineId == null) return;
parseGuidelineParams(orientation, state, guidelineId, (CLObject) params);
}
static void parseGuidelineParams(
int orientation,
State state,
String guidelineId,
CLObject params
) throws CLParsingException {
ArrayList<String> constraints = params.names();
if (constraints == null) return;
ConstraintReference reference = state.constraints(guidelineId);
if (orientation == ConstraintWidget.HORIZONTAL) {
state.horizontalGuideline(guidelineId);
} else {
state.verticalGuideline(guidelineId);
}
// Layout direction may be ignored for Horizontal guidelines (placed along the Y axis),
// since `start` & `end` represent the `top` and `bottom` distances respectively.
boolean isLtr = !state.isRtl() || orientation == ConstraintWidget.HORIZONTAL;
GuidelineReference guidelineReference = (GuidelineReference) reference.getFacade();
// Whether the guideline is based on percentage or distance
boolean isPercent = false;
// Percent or distance value of the guideline
float value = 0f;
// Indicates if the value is considered from the "start" position,
// meaning "left" anchor for vertical guidelines and "top" anchor for
// horizontal guidelines
boolean fromStart = true;
for (String constraintName : constraints) {
switch (constraintName) {
// left and right are here just to support LTR independent vertical guidelines
case "left":
value = toPix(state, params.getFloat(constraintName));
fromStart = true;
break;
case "right":
value = toPix(state, params.getFloat(constraintName));
fromStart = false;
break;
case "start":
value = toPix(state, params.getFloat(constraintName));
fromStart = isLtr;
break;
case "end":
value = toPix(state, params.getFloat(constraintName));
fromStart = !isLtr;
break;
case "percent":
isPercent = true;
CLArray percentParams = params.getArrayOrNull(constraintName);
if (percentParams == null) {
fromStart = true;
value = params.getFloat(constraintName);
} else if (percentParams.size() > 1) {
String origin = percentParams.getString(0);
value = percentParams.getFloat(1);
switch (origin) {
case "left":
fromStart = true;
break;
case "right":
fromStart = false;
break;
case "start":
fromStart = isLtr;
break;
case "end":
fromStart = !isLtr;
break;
}
}
break;
}
}
// Populate the guideline based on the resolved properties
if (isPercent) {
if (fromStart) {
guidelineReference.percent(value);
} else {
guidelineReference.percent(1f - value);
}
} else {
if (fromStart) {
guidelineReference.start(value);
} else {
guidelineReference.end(value);
}
}
}
static void parseBarrier(
State state,
String elementName, CLObject element
) throws CLParsingException {
boolean isLtr = !state.isRtl();
BarrierReference reference = state.barrier(elementName, State.Direction.END);
ArrayList<String> constraints = element.names();
if (constraints == null) {
return;
}
for (String constraintName : constraints) {
switch (constraintName) {
case "direction": {
switch (element.getString(constraintName)) {
case "start":
if (isLtr) {
reference.setBarrierDirection(State.Direction.LEFT);
} else {
reference.setBarrierDirection(State.Direction.RIGHT);
}
break;
case "end":
if (isLtr) {
reference.setBarrierDirection(State.Direction.RIGHT);
} else {
reference.setBarrierDirection(State.Direction.LEFT);
}
break;
case "left":
reference.setBarrierDirection(State.Direction.LEFT);
break;
case "right":
reference.setBarrierDirection(State.Direction.RIGHT);
break;
case "top":
reference.setBarrierDirection(State.Direction.TOP);
break;
case "bottom":
reference.setBarrierDirection(State.Direction.BOTTOM);
break;
}
}
break;
case "margin":
float margin = element.getFloatOrNaN(constraintName);
if (!Float.isNaN(margin)) {
reference.margin(toPix(state, margin));
}
break;
case "contains":
CLArray list = element.getArrayOrNull(constraintName);
if (list != null) {
for (int j = 0; j < list.size(); j++) {
String elementNameReference = list.get(j).content();
ConstraintReference elementReference =
state.constraints(elementNameReference);
if (PARSER_DEBUG) {
System.out.println(
"Add REFERENCE "
+ "($elementNameReference = $elementReference) "
+ "TO BARRIER "
);
}
reference.add(elementReference);
}
}
break;
}
}
}
static void parseWidget(
State state,
LayoutVariables layoutVariables,
String elementName,
CLObject element
) throws CLParsingException {
ConstraintReference reference = state.constraints(elementName);
parseWidget(state, layoutVariables, reference, element);
}
/**
* Set/apply attribute to a widget/helper reference
*
* @param state Constraint State
* @param layoutVariables layout variables
* @param reference widget/helper reference
* @param element the parsed CLObject
* @param attributeName Name of the attribute to be set/applied
* @throws CLParsingException
*/
static void applyAttribute(
State state,
LayoutVariables layoutVariables,
ConstraintReference reference,
CLObject element,
String attributeName) throws CLParsingException {
float value;
switch (attributeName) {
case "width":
reference.setWidth(parseDimension(element,
attributeName, state, state.getDpToPixel()));
break;
case "height":
reference.setHeight(parseDimension(element,
attributeName, state, state.getDpToPixel()));
break;
case "center":
String target = element.getString(attributeName);
ConstraintReference targetReference;
if (target.equals("parent")) {
targetReference = state.constraints(State.PARENT);
} else {
targetReference = state.constraints(target);
}
reference.startToStart(targetReference);
reference.endToEnd(targetReference);
reference.topToTop(targetReference);
reference.bottomToBottom(targetReference);
break;
case "centerHorizontally":
target = element.getString(attributeName);
targetReference = target.equals("parent")
? state.constraints(State.PARENT) : state.constraints(target);
reference.startToStart(targetReference);
reference.endToEnd(targetReference);
break;
case "centerVertically":
target = element.getString(attributeName);
targetReference = target.equals("parent")
? state.constraints(State.PARENT) : state.constraints(target);
reference.topToTop(targetReference);
reference.bottomToBottom(targetReference);
break;
case "alpha":
value = layoutVariables.get(element.get(attributeName));
reference.alpha(value);
break;
case "scaleX":
value = layoutVariables.get(element.get(attributeName));
reference.scaleX(value);
break;
case "scaleY":
value = layoutVariables.get(element.get(attributeName));
reference.scaleY(value);
break;
case "translationX":
value = layoutVariables.get(element.get(attributeName));
reference.translationX(toPix(state, value));
break;
case "translationY":
value = layoutVariables.get(element.get(attributeName));
reference.translationY(toPix(state, value));
break;
case "translationZ":
value = layoutVariables.get(element.get(attributeName));
reference.translationZ(toPix(state, value));
break;
case "pivotX":
value = layoutVariables.get(element.get(attributeName));
reference.pivotX(value);
break;
case "pivotY":
value = layoutVariables.get(element.get(attributeName));
reference.pivotY(value);
break;
case "rotationX":
value = layoutVariables.get(element.get(attributeName));
reference.rotationX(value);
break;
case "rotationY":
value = layoutVariables.get(element.get(attributeName));
reference.rotationY(value);
break;
case "rotationZ":
value = layoutVariables.get(element.get(attributeName));
reference.rotationZ(value);
break;
case "visibility":
switch (element.getString(attributeName)) {
case "visible":
reference.visibility(ConstraintWidget.VISIBLE);
break;
case "invisible":
reference.visibility(ConstraintWidget.INVISIBLE);
reference.alpha(0f);
break;
case "gone":
reference.visibility(ConstraintWidget.GONE);
break;
}
break;
case "vBias":
value = layoutVariables.get(element.get(attributeName));
reference.verticalBias(value);
break;
case "hRtlBias":
// TODO: This is a temporary solution to support bias with start/end constraints,
// where the bias needs to be reversed in RTL, we probably want a better or more
// intuitive way to do this
value = layoutVariables.get(element.get(attributeName));
if (state.isRtl()) {
value = 1f - value;
}
reference.horizontalBias(value);
break;
case "hBias":
value = layoutVariables.get(element.get(attributeName));
reference.horizontalBias(value);
break;
case "vWeight":
value = layoutVariables.get(element.get(attributeName));
reference.setVerticalChainWeight(value);
break;
case "hWeight":
value = layoutVariables.get(element.get(attributeName));
reference.setHorizontalChainWeight(value);
break;
case "custom":
parseCustomProperties(element, reference, attributeName);
break;
case "motion":
parseMotionProperties(element.get(attributeName), reference);
break;
default:
parseConstraint(state, layoutVariables, element, reference, attributeName);
}
}
static void parseWidget(
State state,
LayoutVariables layoutVariables,
ConstraintReference reference,
CLObject element
) throws CLParsingException {
if (reference.getWidth() == null) {
// Default to Wrap when the Dimension has not been assigned
reference.setWidth(Dimension.createWrap());
}
if (reference.getHeight() == null) {
// Default to Wrap when the Dimension has not been assigned
reference.setHeight(Dimension.createWrap());
}
ArrayList<String> constraints = element.names();
if (constraints == null) {
return;
}
for (String constraintName : constraints) {
applyAttribute(state, layoutVariables, reference, element, constraintName);
}
}
static void parseCustomProperties(
CLObject element,
ConstraintReference reference,
String constraintName
) throws CLParsingException {
CLObject json = element.getObjectOrNull(constraintName);
if (json == null) {
return;
}
ArrayList<String> properties = json.names();
if (properties == null) {
return;
}
for (String property : properties) {
CLElement value = json.get(property);
if (value instanceof CLNumber) {
reference.addCustomFloat(property, value.getFloat());
} else if (value instanceof CLString) {
long it = parseColorString(value.content());
if (it != -1) {
reference.addCustomColor(property, (int) it);
}
}
}
}
private static int indexOf(String val, String... types) {
for (int i = 0; i < types.length; i++) {
if (types[i].equals(val)) {
return i;
}
}
return -1;
}
/**
* parse the motion section of a constraint
* <pre>
* csetName: {
* idToConstrain : {
* motion: {
* pathArc : 'startVertical'
* relativeTo: 'id'
* easing: 'curve'
* stagger: '2'
* quantize: steps or [steps, 'interpolator' phase ]
* }
* }
* }
* </pre>
*/
private static void parseMotionProperties(
CLElement element,
ConstraintReference reference
) throws CLParsingException {
if (!(element instanceof CLObject)) {
return;
}
CLObject obj = (CLObject) element;
TypedBundle bundle = new TypedBundle();
ArrayList<String> constraints = obj.names();
if (constraints == null) {
return;
}
for (String constraintName : constraints) {
switch (constraintName) {
case "pathArc":
String val = obj.getString(constraintName);
int ord = indexOf(val, "none", "startVertical", "startHorizontal", "flip",
"below", "above");
if (ord == -1) {
System.err.println(obj.getLine() + " pathArc = '" + val + "'");
break;
}
bundle.add(TypedValues.MotionType.TYPE_PATHMOTION_ARC, ord);
break;
case "relativeTo":
bundle.add(TypedValues.MotionType.TYPE_ANIMATE_RELATIVE_TO,
obj.getString(constraintName));
break;
case "easing":
bundle.add(TypedValues.MotionType.TYPE_EASING, obj.getString(constraintName));
break;
case "stagger":
bundle.add(TypedValues.MotionType.TYPE_STAGGER, obj.getFloat(constraintName));
break;
case "quantize":
CLElement quant = obj.get(constraintName);
if (quant instanceof CLArray) {
CLArray array = (CLArray) quant;
int len = array.size();
if (len > 0) {
bundle.add(TYPE_QUANTIZE_MOTIONSTEPS, array.getInt(0));
if (len > 1) {
bundle.add(TYPE_QUANTIZE_INTERPOLATOR_TYPE, array.getString(1));
if (len > 2) {
bundle.add(TYPE_QUANTIZE_MOTION_PHASE, array.getFloat(2));
}
}
}
} else {
bundle.add(TYPE_QUANTIZE_MOTIONSTEPS, obj.getInt(constraintName));
}
break;
}
}
reference.mMotionProperties = bundle;
}
static void parseConstraint(
State state,
LayoutVariables layoutVariables,
CLObject element,
ConstraintReference reference,
String constraintName
) throws CLParsingException {
boolean isLtr = !state.isRtl();
CLArray constraint = element.getArrayOrNull(constraintName);
if (constraint != null && constraint.size() > 1) {
// params: target, anchor
String target = constraint.getString(0);
String anchor = constraint.getStringOrNull(1);
float margin = 0f;
float marginGone = 0f;
if (constraint.size() > 2) {
// params: target, anchor, margin
CLElement arg2 = constraint.getOrNull(2);
margin = layoutVariables.get(arg2);
margin = toPix(state, margin);
}
if (constraint.size() > 3) {
// params: target, anchor, margin, marginGone
CLElement arg2 = constraint.getOrNull(3);
marginGone = layoutVariables.get(arg2);
marginGone = toPix(state, marginGone);
}
ConstraintReference targetReference = target.equals("parent")
? state.constraints(State.PARENT) :
state.constraints(target);
// For simplicity, we'll apply horizontal constraints separately
boolean isHorizontalConstraint = false;
boolean isHorOriginLeft = true;
boolean isHorTargetLeft = true;
switch (constraintName) {
case "circular":
float angle = layoutVariables.get(constraint.get(1));
float distance = 0f;
if (constraint.size() > 2) {
CLElement distanceArg = constraint.getOrNull(2);
distance = layoutVariables.get(distanceArg);
distance = toPix(state, distance);
}
reference.circularConstraint(targetReference, angle, distance);
break;
case "top":
switch (anchor) {
case "top":
reference.topToTop(targetReference);
break;
case "bottom":
reference.topToBottom(targetReference);
break;
case "baseline":
state.baselineNeededFor(targetReference.getKey());
reference.topToBaseline(targetReference);
break;
}
break;
case "bottom":
switch (anchor) {
case "top":
reference.bottomToTop(targetReference);
break;
case "bottom":
reference.bottomToBottom(targetReference);
break;
case "baseline":
state.baselineNeededFor(targetReference.getKey());
reference.bottomToBaseline(targetReference);
}
break;
case "baseline":
switch (anchor) {
case "baseline":
state.baselineNeededFor(reference.getKey());
state.baselineNeededFor(targetReference.getKey());
reference.baselineToBaseline(targetReference);
break;
case "top":
state.baselineNeededFor(reference.getKey());
reference.baselineToTop(targetReference);
break;
case "bottom":
state.baselineNeededFor(reference.getKey());
reference.baselineToBottom(targetReference);
break;
}
break;
case "left":
isHorizontalConstraint = true;
isHorOriginLeft = true;
break;
case "right":
isHorizontalConstraint = true;
isHorOriginLeft = false;
break;
case "start":
isHorizontalConstraint = true;
isHorOriginLeft = isLtr;
break;
case "end":
isHorizontalConstraint = true;
isHorOriginLeft = !isLtr;
break;
}
if (isHorizontalConstraint) {
// Resolve horizontal target anchor
switch (anchor) {
case "left":
isHorTargetLeft = true;
break;
case "right":
isHorTargetLeft = false;
break;
case "start":
isHorTargetLeft = isLtr;
break;
case "end":
isHorTargetLeft = !isLtr;
break;
}
// Resolved anchors, apply corresponding constraint
if (isHorOriginLeft) {
if (isHorTargetLeft) {
reference.leftToLeft(targetReference);
} else {
reference.leftToRight(targetReference);
}
} else {
if (isHorTargetLeft) {
reference.rightToLeft(targetReference);
} else {
reference.rightToRight(targetReference);
}
}
}
reference.margin(margin).marginGone(marginGone);
} else {
String target = element.getStringOrNull(constraintName);
if (target != null) {
ConstraintReference targetReference = target.equals("parent")
? state.constraints(State.PARENT) :
state.constraints(target);
switch (constraintName) {
case "start":
if (isLtr) {
reference.leftToLeft(targetReference);
} else {
reference.rightToRight(targetReference);
}
break;
case "end":
if (isLtr) {
reference.rightToRight(targetReference);
} else {
reference.leftToLeft(targetReference);
}
break;
case "top":
reference.topToTop(targetReference);
break;
case "bottom":
reference.bottomToBottom(targetReference);
break;
case "baseline":
state.baselineNeededFor(reference.getKey());
state.baselineNeededFor(targetReference.getKey());
reference.baselineToBaseline(targetReference);
break;
}
}
}
}
static Dimension parseDimensionMode(String dimensionString) {
Dimension dimension = Dimension.createFixed(0);
switch (dimensionString) {
case "wrap":
dimension = Dimension.createWrap();
break;
case "preferWrap":
dimension = Dimension.createSuggested(Dimension.WRAP_DIMENSION);
break;
case "spread":
dimension = Dimension.createSuggested(Dimension.SPREAD_DIMENSION);
break;
case "parent":
dimension = Dimension.createParent();
break;
default: {
if (dimensionString.endsWith("%")) {
// parent percent
String percentString =
dimensionString.substring(0, dimensionString.indexOf('%'));
float percentValue = Float.parseFloat(percentString) / 100f;
dimension = Dimension.createPercent(0, percentValue).suggested(0);
} else if (dimensionString.contains(":")) {
dimension = Dimension.createRatio(dimensionString)
.suggested(Dimension.SPREAD_DIMENSION);
}
}
}
return dimension;
}
static Dimension parseDimension(CLObject element,
String constraintName,
State state,
CorePixelDp dpToPixels) throws CLParsingException {
CLElement dimensionElement = element.get(constraintName);
Dimension dimension = Dimension.createFixed(0);
if (dimensionElement instanceof CLString) {
dimension = parseDimensionMode(dimensionElement.content());
} else if (dimensionElement instanceof CLNumber) {
dimension = Dimension.createFixed(
state.convertDimension(dpToPixels.toPixels(element.getFloat(constraintName))));
} else if (dimensionElement instanceof CLObject) {
CLObject obj = (CLObject) dimensionElement;
String mode = obj.getStringOrNull("value");
if (mode != null) {
dimension = parseDimensionMode(mode);
}
CLElement minEl = obj.getOrNull("min");
if (minEl != null) {
if (minEl instanceof CLNumber) {
float min = ((CLNumber) minEl).getFloat();
dimension.min(state.convertDimension(dpToPixels.toPixels(min)));
} else if (minEl instanceof CLString) {
dimension.min(Dimension.WRAP_DIMENSION);
}
}
CLElement maxEl = obj.getOrNull("max");
if (maxEl != null) {
if (maxEl instanceof CLNumber) {
float max = ((CLNumber) maxEl).getFloat();
dimension.max(state.convertDimension(dpToPixels.toPixels(max)));
} else if (maxEl instanceof CLString) {
dimension.max(Dimension.WRAP_DIMENSION);
}
}
}
return dimension;
}
/**
* parse a color string
*
* @return -1 if it cannot parse unsigned long
*/
static long parseColorString(String value) {
String str = value;
if (str.startsWith("#")) {
str = str.substring(1);
if (str.length() == 6) {
str = "FF" + str;
}
return Long.parseLong(str, 16);
} else {
return -1L;
}
}
static String lookForType(CLObject element) throws CLParsingException {
ArrayList<String> constraints = element.names();
for (String constraintName : constraints) {
if (constraintName.equals("type")) {
return element.getString("type");
}
}
return null;
}
}