Constraint.java

/*
 * 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.dsl;

import java.util.HashMap;
import java.util.Map;

/**
 * Provides the API for creating a Constraint Object for use in the Core
 * ConstraintLayout & MotionLayout system
 */
public class Constraint {

    private final String mId;
    public static final Constraint PARENT = new Constraint("parent");

    public Constraint(String id) {
        mId = id;
    }

    public class VAnchor extends Anchor {
        VAnchor(VSide side) {
            super(Side.valueOf(side.name()));
        }
    }

    public class HAnchor extends Anchor {
        HAnchor(HSide side) {
            super(Side.valueOf(side.name()));
        }
    }

    public class Anchor {
        final Side mSide;
        Anchor mConnection = null;
        int mMargin;
        int mGoneMargin = Integer.MIN_VALUE;

        Anchor(Side side) {
            mSide = side;
        }

        public String getId() {
            return mId;
        }

        Constraint getParent() {
            return Constraint.this;
        }

        public void build(StringBuilder builder) {
            if (mConnection != null) {
                builder.append(mSide.toString().toLowerCase())
                        .append(":").append(this).append(",\n");
            }
        }

        @Override
        public String toString() {
            StringBuilder ret = new StringBuilder("[");

            if (mConnection != null) {
                ret.append("'").append(mConnection.getId()).append("',")
                        .append("'").append(mConnection.mSide.toString().toLowerCase()).append("'");
            }

            if (mMargin != 0) {
                ret.append(",").append(mMargin);
            }

            if (mGoneMargin != Integer.MIN_VALUE) {
                if ( mMargin == 0) {
                    ret.append(",0,").append(mGoneMargin);
                } else {
                    ret.append(",").append(mGoneMargin);
                }
            }

            ret.append("]");
            return ret.toString();
        }
    }

    public enum Behaviour {
        SPREAD,
        WRAP,
        PERCENT,
        RATIO,
        RESOLVED,
    }

    public enum ChainMode {
        SPREAD,
        SPREAD_INSIDE,
        PACKED,
    }

    public enum VSide {
        TOP,
        BOTTOM,
        BASELINE
    }

    public enum HSide {
        LEFT,
        RIGHT,
        START,
        END
    }

    public enum Side {
        LEFT,
        RIGHT,
        TOP,
        BOTTOM,
        START,
        END,
        BASELINE
    }

    static int UNSET = Integer.MIN_VALUE;
    static Map<ChainMode, String> chainModeMap = new HashMap<>();
    static {
        chainModeMap.put(ChainMode.SPREAD, "spread");
        chainModeMap.put(ChainMode.SPREAD_INSIDE, "spread_inside");
        chainModeMap.put(ChainMode.PACKED, "packed");
    }

    String helperType = null;
    String helperJason = null;

    private HAnchor mLeft = new HAnchor(HSide.LEFT);
    private HAnchor mRight = new HAnchor(HSide.RIGHT);
    private VAnchor mTop = new VAnchor(VSide.TOP);
    private VAnchor mBottom = new VAnchor(VSide.BOTTOM);
    private HAnchor mStart = new HAnchor(HSide.START);
    private HAnchor mEnd = new HAnchor(HSide.END);
    private VAnchor mBaseline = new VAnchor(VSide.BASELINE);
    private int mWidth = UNSET;
    private int mHeight = UNSET;
    private float mHorizontalBias = Float.NaN;
    private float mVerticalBias = Float.NaN;
    private String mDimensionRatio = null;
    private String mCircleConstraint = null;
    private int mCircleRadius = Integer.MIN_VALUE;
    private float mCircleAngle = Float.NaN;
    private int mEditorAbsoluteX = Integer.MIN_VALUE;
    private int mEditorAbsoluteY = Integer.MIN_VALUE;
    private float mVerticalWeight = Float.NaN;
    private float mHorizontalWeight = Float.NaN;
    private ChainMode mHorizontalChainStyle = null;
    private ChainMode mVerticalChainStyle = null;
    private Behaviour mWidthDefault = null;
    private Behaviour mHeightDefault = null;
    private int mWidthMax = UNSET;
    private int mHeightMax = UNSET;
    private int mWidthMin = UNSET;
    private int mHeightMin = UNSET;
    private float mWidthPercent = Float.NaN;
    private float mHeightPercent = Float.NaN;
    private String[] mReferenceIds = null;
    private boolean mConstrainedWidth = false;
    private boolean mConstrainedHeight = false;

    /**
     * get left anchor
     *
     * @return left anchor
     */
    public HAnchor getLeft() {
        return mLeft;
    }

    /**
     * get right anchor
     *
     * @return right anchor
     */
    public HAnchor getRight() {
        return mRight;
    }

    /**
     * get top anchor
     *
     * @return top anchor
     */
    public VAnchor getTop() {
        return mTop;
    }

    /**
     * get bottom anchor
     *
     * @return bottom anchor
     */
    public VAnchor getBottom() {
        return mBottom;
    }

    /**
     * get start anchor
     *
     * @return start anchor
     */
    public HAnchor getStart() {
        return mStart;
    }

    /**
     * get end anchor
     *
     * @return end anchor
     */
    public HAnchor getEnd() {
        return mEnd;
    }

    /**
     * get baseline anchor
     *
     * @return baseline anchor
     */
    public VAnchor getBaseline() {
        return mBaseline;
    }

    /**
     * get horizontalBias
     *
     * @return horizontalBias
     */
    public float getHorizontalBias() {
        return mHorizontalBias;
    }

    /**
     * set horizontalBias
     *
     * @param horizontalBias
     */
    public void setHorizontalBias(float horizontalBias) {
        this.mHorizontalBias = horizontalBias;
    }

    /**
     * get verticalBias
     *
     * @return verticalBias
     */
    public float getVerticalBias() {
        return mVerticalBias;
    }

    /**
     * set verticalBias
     *
     * @param verticalBias
     */
    public void setVerticalBias(float verticalBias) {
        this.mVerticalBias = verticalBias;
    }

    /**
     * get dimensionRatio
     *
     * @return dimensionRatio
     */
    public String getDimensionRatio() {
        return mDimensionRatio;
    }

    /**
     * set dimensionRatio
     *
     * @param dimensionRatio
     */
    public void setDimensionRatio(String dimensionRatio) {
        this.mDimensionRatio = dimensionRatio;
    }

    /**
     * get circleConstraint
     *
     * @return circleConstraint
     */
    public String getCircleConstraint() {
        return mCircleConstraint;
    }

    /**
     * set circleConstraint
     *
     * @param circleConstraint
     */
    public void setCircleConstraint(String circleConstraint) {
        this.mCircleConstraint = circleConstraint;
    }

    /**
     * get circleRadius
     *
     * @return circleRadius
     */
    public int getCircleRadius() {
        return mCircleRadius;
    }

    /**
     * set circleRadius
     *
     * @param circleRadius
     */
    public void setCircleRadius(int circleRadius) {
        this.mCircleRadius = circleRadius;
    }

    /**
     * get circleAngle
     *
     * @return circleAngle
     */
    public float getCircleAngle() {
        return mCircleAngle;
    }

    /**
     * set circleAngle
     *
     * @param circleAngle
     */
    public void setCircleAngle(float circleAngle) {
        this.mCircleAngle = circleAngle;
    }

    /**
     * get editorAbsoluteX
     * @return editorAbsoluteX
     */
    public int getEditorAbsoluteX() {
        return mEditorAbsoluteX;
    }

    /**
     * set editorAbsoluteX
     * @param editorAbsoluteX
     */
    public void setEditorAbsoluteX(int editorAbsoluteX) {
        mEditorAbsoluteX = editorAbsoluteX;
    }

    /**
     * get editorAbsoluteY
     * @return editorAbsoluteY
     */
    public int getEditorAbsoluteY() {
        return mEditorAbsoluteY;
    }

    /**
     * set editorAbsoluteY
     * @param editorAbsoluteY
     */
    public void setEditorAbsoluteY(int editorAbsoluteY) {
        mEditorAbsoluteY = editorAbsoluteY;
    }

    /**
     * get verticalWeight
     *
     * @return verticalWeight
     */
    public float getVerticalWeight() {
        return mVerticalWeight;
    }

    /**
     * set verticalWeight
     *
     * @param verticalWeight
     */
    public void setVerticalWeight(float verticalWeight) {
        this.mVerticalWeight = verticalWeight;
    }

    /**
     * get horizontalWeight
     *
     * @return horizontalWeight
     */
    public float getHorizontalWeight() {
        return mHorizontalWeight;
    }

    /**
     * set horizontalWeight
     *
     * @param horizontalWeight
     */
    public void setHorizontalWeight(float horizontalWeight) {
        this.mHorizontalWeight = horizontalWeight;
    }

    /**
     * get horizontalChainStyle
     *
     * @return horizontalChainStyle
     */
    public ChainMode getHorizontalChainStyle() {
        return mHorizontalChainStyle;
    }

    /**
     * set horizontalChainStyle
     *
     * @param horizontalChainStyle
     */
    public void setHorizontalChainStyle(
            ChainMode horizontalChainStyle) {
        this.mHorizontalChainStyle = horizontalChainStyle;
    }

    /**
     * get verticalChainStyle
     *
     * @return verticalChainStyle
     */
    @SuppressWarnings("HiddenTypeParameter")
    public ChainMode getVerticalChainStyle() {
        return mVerticalChainStyle;
    }

    /**
     * set verticalChainStyle
     *
     * @param verticalChainStyle
     */
    public void setVerticalChainStyle(
            @SuppressWarnings("HiddenTypeParameter") ChainMode verticalChainStyle) {
        this.mVerticalChainStyle = verticalChainStyle;
    }

    /**
     * get widthDefault
     *
     * @return widthDefault
     */
    @SuppressWarnings("HiddenTypeParameter")
    public Behaviour getWidthDefault() {
        return mWidthDefault;
    }

    /**
     * set widthDefault
     *
     * @param widthDefault
     */
    public void setWidthDefault(@SuppressWarnings("HiddenTypeParameter")
            Behaviour widthDefault) {
        this.mWidthDefault = widthDefault;
    }

    /**
     * get heightDefault
     *
     * @return heightDefault
     */
    @SuppressWarnings("HiddenTypeParameter")
    public Behaviour getHeightDefault() {
        return mHeightDefault;
    }

    /**
     * set heightDefault
     *
     * @param heightDefault
     */
    public void setHeightDefault(@SuppressWarnings("HiddenTypeParameter")
            Behaviour heightDefault) {
        this.mHeightDefault = heightDefault;
    }

    /**
     * get widthMax
     *
     * @return widthMax
     */
    public int getWidthMax() {
        return mWidthMax;
    }

    /**
     * set widthMax
     *
     * @param widthMax
     */
    public void setWidthMax(int widthMax) {
        this.mWidthMax = widthMax;
    }

    /**
     * get heightMax
     *
     * @return heightMax
     */
    public int getHeightMax() {
        return mHeightMax;
    }

    /**
     * set heightMax
     *
     * @param heightMax
     */
    public void setHeightMax(int heightMax) {
        this.mHeightMax = heightMax;
    }

    /**
     * get widthMin
     *
     * @return widthMin
     */
    public int getWidthMin() {
        return mWidthMin;
    }

    /**
     * set widthMin
     *
     * @param widthMin
     */
    public void setWidthMin(int widthMin) {
        this.mWidthMin = widthMin;
    }

    /**
     * get heightMin
     *
     * @return heightMin
     */
    public int getHeightMin() {
        return mHeightMin;
    }

    /**
     * set heightMin
     *
     * @param heightMin
     */
    public void setHeightMin(int heightMin) {
        this.mHeightMin = heightMin;
    }

    /**
     * get widthPercent
     *
     * @return
     */
    public float getWidthPercent() {
        return mWidthPercent;
    }

    /**
     * set widthPercent
     *
     * @param widthPercent
     */
    public void setWidthPercent(float widthPercent) {
        this.mWidthPercent = widthPercent;
    }

    /**
     * get heightPercent
     *
     * @return heightPercent
     */
    public float getHeightPercent() {
        return mHeightPercent;
    }

    /**
     * set heightPercent
     *
     * @param heightPercent
     */
    public void setHeightPercent(float heightPercent) {
        this.mHeightPercent = heightPercent;
    }

    /**
     * get referenceIds
     *
     * @return referenceIds
     */
    public String[] getReferenceIds() {
        return mReferenceIds;
    }

    /**
     * set referenceIds
     *
     * @param referenceIds
     */
    public void setReferenceIds(String[] referenceIds) {
        mReferenceIds = referenceIds;
    }

    /**
     * is constrainedWidth
     *
     * @return true if width constrained
     */
    public boolean isConstrainedWidth() {
        return mConstrainedWidth;
    }

    /**
     * set constrainedWidth
     *
     * @param constrainedWidth
     */
    public void setConstrainedWidth(boolean constrainedWidth) {
        this.mConstrainedWidth = constrainedWidth;
    }

    /**
     * is constrainedHeight
     *
     * @return true if height constrained
     */
    public boolean isConstrainedHeight() {
        return mConstrainedHeight;
    }

    /**
     * set constrainedHeight
     *
     * @param constrainedHeight
     */
    public void setConstrainedHeight(boolean constrainedHeight) {
        this.mConstrainedHeight = constrainedHeight;
    }

    /**
     * get width
     * @return width
     */
    public int getWidth() {
        return mWidth;
    }

    /**
     * set width
     *
     * @param width
     */
    public void setWidth(int width) {
        mWidth = width;
    }

    /**
     * get height
     * @return height
     */
    public int getHeight() {
        return mHeight;
    }

    /**
     * set height
     *
     * @param height
     */
    public void setHeight(int height) {
        mHeight = height;
    }

    /**
     * Connect anchor to Top
     *
     * @param anchor anchor to be connected
     */
    public void linkToTop(VAnchor anchor) {
        linkToTop(anchor, 0);
    }

    /**
     * Connect anchor to Left
     *
     * @param anchor anchor to be connected
     */
    public void linkToLeft(HAnchor anchor) {
        linkToLeft(anchor, 0);
    }

    /**
     * Connect anchor to Right
     *
     * @param anchor anchor to be connected
     */
    public void linkToRight(HAnchor anchor) {
        linkToRight(anchor, 0);
    }

    /**
     * Connect anchor to Start
     *
     * @param anchor anchor to be connected
     */
    public void linkToStart(HAnchor anchor) {
        linkToStart(anchor, 0);
    }

    /**
     * Connect anchor to End
     *
     * @param anchor anchor to be connected
     */
    public void linkToEnd(HAnchor anchor) {
        linkToEnd(anchor, 0);
    }

    /**
     * Connect anchor to Bottom
     *
     * @param anchor anchor to be connected
     */
    public void linkToBottom(VAnchor anchor) {
        linkToBottom(anchor, 0);
    }

    /**
     * Connect anchor to Baseline
     *
     * @param anchor anchor to be connected
     */
    public void linkToBaseline(VAnchor anchor) {
        linkToBaseline(anchor, 0);
    }

    /**
     * Connect anchor to Top
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     */
    public void linkToTop(VAnchor anchor, int margin) {
        linkToTop(anchor, margin, Integer.MIN_VALUE);
    }

    /**
     * Connect anchor to Left
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     */
    public void linkToLeft(HAnchor anchor, int margin) {
        linkToLeft(anchor, margin, Integer.MIN_VALUE);
    }

    /**
     * Connect anchor to Right
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     */
    public void linkToRight(HAnchor anchor, int margin) {
        linkToRight(anchor, margin, Integer.MIN_VALUE);
    }

    /**
     * Connect anchor to Start
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     */
    public void linkToStart(HAnchor anchor, int margin) {
        linkToStart(anchor, margin, Integer.MIN_VALUE);
    }

    /**
     * Connect anchor to End
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     */
    public void linkToEnd(HAnchor anchor, int margin) {
        linkToEnd(anchor, margin, Integer.MIN_VALUE);
    }

    /**
     * Connect anchor to Bottom
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     */
    public void linkToBottom(VAnchor anchor, int margin) {
        linkToBottom(anchor, margin, Integer.MIN_VALUE);
    }

    /**
     * Connect anchor to Baseline
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     */
    public void linkToBaseline(VAnchor anchor, int margin) {
        linkToBaseline(anchor, margin, Integer.MIN_VALUE);
    }

    /**
     * Connect anchor to Top
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     * @param goneMargin value of the goneMargin
     */
    public void linkToTop(VAnchor anchor, int margin, int goneMargin) {
        mTop.mConnection = anchor;
        mTop.mMargin = margin;
        mTop.mGoneMargin = goneMargin;
    }

    /**
     * Connect anchor to Left
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     * @param goneMargin value of the goneMargin
     */
    public void linkToLeft(HAnchor anchor, int margin, int goneMargin) {
        mLeft.mConnection = anchor;
        mLeft.mMargin = margin;
        mLeft.mGoneMargin = goneMargin;
    }

    /**
     * Connect anchor to Right
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     * @param goneMargin value of the goneMargin
     */
    public void linkToRight(HAnchor anchor, int margin, int goneMargin) {
        mRight.mConnection = anchor;
        mRight.mMargin = margin;
        mRight.mGoneMargin = goneMargin;
    }

    /**
     * Connect anchor to Start
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     * @param goneMargin value of the goneMargin
     */
    public void linkToStart(HAnchor anchor, int margin, int goneMargin) {
        mStart.mConnection = anchor;
        mStart.mMargin = margin;
        mStart.mGoneMargin = goneMargin;
    }

    /**
     * Connect anchor to End
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     * @param goneMargin value of the goneMargin
     */
    public void linkToEnd(HAnchor anchor, int margin, int goneMargin) {
        mEnd.mConnection = anchor;
        mEnd.mMargin = margin;
        mEnd.mGoneMargin = goneMargin;
    }

    /**
     * Connect anchor to Bottom
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     * @param goneMargin value of the goneMargin
     */
    public void linkToBottom(VAnchor anchor, int margin, int goneMargin) {
        mBottom.mConnection = anchor;
        mBottom.mMargin = margin;
        mBottom.mGoneMargin = goneMargin;
    }

    /**
     * Connect anchor to Baseline
     *
     * @param anchor anchor to be connected
     * @param margin value of the margin
     * @param goneMargin value of the goneMargin
     */
    public void linkToBaseline(VAnchor anchor, int margin, int goneMargin) {
        mBaseline.mConnection = anchor;
        mBaseline.mMargin = margin;
        mBaseline.mGoneMargin = goneMargin;
    }

    /**
     * convert a String array into a String representation
     *
     * @param str String array to be converted
     * @return a String representation of the input array.
     */
    public String convertStringArrayToString(String[] str) {
        StringBuilder ret = new StringBuilder("[");
        for (int i = 0; i < str.length; i++) {

            ret.append((i == 0) ? "'" : ",'");

            ret.append(str[i]);
            ret.append("'");

        }
        ret.append("]");
        return ret.toString();
    }

    protected void append(StringBuilder builder, String name, float value) {
        if (Float.isNaN(value)) {
            return;
        }
        builder.append(name);
        builder.append(":").append(value).append(",\n");

    }

    @Override
    public String toString() {
        StringBuilder ret = new StringBuilder(mId + ":{\n");
        mLeft.build(ret);
        mRight.build(ret);
        mTop.build(ret);
        mBottom.build(ret);
        mStart.build(ret);
        mEnd.build(ret);
        mBaseline.build(ret);

        if (mWidth != UNSET) {
            ret.append("width:").append(mWidth).append(",\n");
        }
        if (mHeight != UNSET) {
            ret.append("height:").append(mHeight).append(",\n");
        }
        append(ret, "horizontalBias", mHorizontalBias);
        append(ret, "verticalBias", mVerticalBias);
        if (mDimensionRatio != null) {
            ret.append("dimensionRatio:'").append(mDimensionRatio).append("',\n");
        }
        if (mCircleConstraint != null) {
            if (!Float.isNaN(mCircleAngle) || mCircleRadius != Integer.MIN_VALUE) {
                ret.append("circular:['").append(mCircleConstraint).append("'");
                if (!Float.isNaN(mCircleAngle)) {
                    ret.append(",").append(mCircleAngle);
                }
                if (mCircleRadius != Integer.MIN_VALUE) {
                    if (Float.isNaN(mCircleAngle)) {
                        ret.append(",0,").append(mCircleRadius);
                    } else {
                        ret.append(",").append(mCircleRadius);
                    }
                }
                ret.append("],\n");
            }
        }
        append(ret, "verticalWeight", mVerticalWeight);
        append(ret, "horizontalWeight", mHorizontalWeight);
        if (mHorizontalChainStyle != null) {
            ret.append("horizontalChainStyle:'").append(chainModeMap.get(mHorizontalChainStyle))
                    .append("',\n");
        }
        if (mVerticalChainStyle != null) {
            ret.append("verticalChainStyle:'").append(chainModeMap.get(mVerticalChainStyle))
                    .append("',\n");
        }
        if (mWidthDefault != null) {
            if (mWidthMax == UNSET && mWidthMin == UNSET) {
                ret.append("width:'").append(mWidthDefault.toString().toLowerCase())
                        .append("',\n");
            } else {
                ret.append("width:{value:'").append(mWidthDefault.toString().toLowerCase())
                        .append("'");
                if (mWidthMax != UNSET) {
                    ret.append(",max:").append(mWidthMax);
                }
                if (mWidthMin != UNSET) {
                    ret.append(",min:").append(mWidthMin);
                }
                ret.append("},\n");
            }
        }
        if (mHeightDefault != null) {
            if (mHeightMax == UNSET && mHeightMin == UNSET) {
                ret.append("height:'").append(mHeightDefault.toString().toLowerCase())
                        .append("',\n");
            } else {
                ret.append("height:{value:'").append(mHeightDefault.toString().toLowerCase())
                        .append("'");
                if (mHeightMax != UNSET) {
                    ret.append(",max:").append(mHeightMax);
                }
                if (mHeightMin != UNSET) {
                    ret.append(",min:").append(mHeightMin);
                }
                ret.append("},\n");
            }
        }
        if (!Double.isNaN(mWidthPercent)) {
            ret.append("width:'").append((int) mWidthPercent).append("%',\n");
        }
        if (!Double.isNaN(mHeightPercent)) {
            ret.append("height:'").append((int) mHeightPercent).append("%',\n");
        }
        if (mReferenceIds != null) {
            ret.append("referenceIds:")
                    .append(convertStringArrayToString(mReferenceIds))
                    .append(",\n");
        }
        if (mConstrainedWidth) {
            ret.append("constrainedWidth:").append(mConstrainedWidth).append(",\n");
        }
        if (mConstrainedHeight) {
            ret.append("constrainedHeight:").append(mConstrainedHeight).append(",\n");
        }

        ret.append("},\n");
        return ret.toString();
    }
}