Barrier.java

/*
 * Copyright (C) 2017 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.widgets;

import androidx.constraintlayout.core.LinearSystem;
import androidx.constraintlayout.core.SolverVariable;

import java.util.HashMap;

/**
 * A Barrier takes multiple widgets
 */
public class Barrier extends HelperWidget {

    public static final int LEFT = 0;
    public static final int RIGHT = 1;
    public static final int TOP = 2;
    public static final int BOTTOM = 3;
    private static final boolean USE_RESOLUTION = true;
    private static final boolean USE_RELAX_GONE = false;

    private int mBarrierType = LEFT;

    private boolean mAllowsGoneWidget = true;
    private int mMargin = 0;
    boolean resolved = false;

    public Barrier() {}
    public Barrier(String debugName) {
        setDebugName(debugName);
    }

    @Override
    public boolean allowedInBarrier() {
        return true;
    }

    public int getBarrierType() { return mBarrierType; }

    public void setBarrierType(int barrierType) {
        mBarrierType = barrierType;
    }

    public void setAllowsGoneWidget(boolean allowsGoneWidget) { mAllowsGoneWidget = allowsGoneWidget; }

    /**
     * Find if this barrier supports gone widgets.
     *
     * @return true if this barrier supports gone widgets, otherwise false
     *
     * @deprecated This method should be called {@code getAllowsGoneWidget} such that {@code allowsGoneWidget}
     * can be accessed as a property from Kotlin; {@see https://android.github.io/kotlin-guides/interop.html#property-prefixes}.
     * Use {@link #getAllowsGoneWidget()} instead.
     */
    @Deprecated
    public boolean allowsGoneWidget() { return mAllowsGoneWidget; }

    /**
     * Find if this barrier supports gone widgets.
     *
     * @return true if this barrier supports gone widgets, otherwise false
     */
    public boolean getAllowsGoneWidget() { return mAllowsGoneWidget; }

    public boolean isResolvedHorizontally() {
        return resolved;
    }

    public boolean isResolvedVertically() {
        return resolved;
    }

    @Override
    public void copy(ConstraintWidget src, HashMap<ConstraintWidget,ConstraintWidget> map) {
        super.copy(src, map);
        Barrier srcBarrier = (Barrier) src;
        mBarrierType = srcBarrier.mBarrierType;
        mAllowsGoneWidget = srcBarrier.mAllowsGoneWidget;
        mMargin = srcBarrier.mMargin;
    }

    @Override
    public String toString() {
        String debug = "[Barrier] " + getDebugName() + " {";
        for (int i = 0; i < mWidgetsCount; i++) {
            ConstraintWidget widget = mWidgets[i];
            if (i > 0) {
                debug += ", ";
            }
            debug += widget.getDebugName();
        }
        debug += "}";
        return debug;
    }

    protected void markWidgets() {
        for (int i = 0; i < mWidgetsCount; i++) {
            ConstraintWidget widget = mWidgets[i];
            if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
                continue;
            }
            if (mBarrierType == LEFT || mBarrierType == RIGHT) {
                widget.setInBarrier(HORIZONTAL, true);
            } else if (mBarrierType == TOP || mBarrierType == BOTTOM) {
                widget.setInBarrier(VERTICAL, true);
            }
        }
    }

    /**
     * Add this widget to the solver
     *
     * @param system the solver we want to add the widget to
     * @param optimize true if {@link Optimizer#OPTIMIZATION_GRAPH} is on
     */
    @Override
    public void addToSolver(LinearSystem system, boolean optimize) {
        if (LinearSystem.FULL_DEBUG) {
            System.out.println("\n----------------------------------------------");
            System.out.println("-- adding " + getDebugName() + " to the solver");
            System.out.println("----------------------------------------------\n");
        }

        ConstraintAnchor position;
        mListAnchors[LEFT] = mLeft;
        mListAnchors[TOP] = mTop;
        mListAnchors[RIGHT] = mRight;
        mListAnchors[BOTTOM] = mBottom;
        for (int i = 0; i < mListAnchors.length; i++) {
            mListAnchors[i].mSolverVariable = system.createObjectVariable(mListAnchors[i]);
        }
        if (mBarrierType >= 0 && mBarrierType < 4) {
            position = mListAnchors[mBarrierType];
        } else {
            return;
        }

        if (USE_RESOLUTION) {
            if (!resolved) {
                allSolved();
            }
            if (resolved) {
                resolved = false;
                if (mBarrierType == LEFT || mBarrierType == RIGHT) {
                    system.addEquality(mLeft.mSolverVariable, mX);
                    system.addEquality(mRight.mSolverVariable, mX);
                } else if (mBarrierType == TOP || mBarrierType == BOTTOM) {
                    system.addEquality(mTop.mSolverVariable, mY);
                    system.addEquality(mBottom.mSolverVariable, mY);
                }
                return;
            }
        }

        // We have to handle the case where some of the elements referenced in the barrier are set as
        // match_constraint; we have to take it in account to set the strength of the barrier.
        boolean hasMatchConstraintWidgets = false;
        for (int i = 0; i < mWidgetsCount; i++) {
            ConstraintWidget widget = mWidgets[i];
            if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
                continue;
            }
            if ((mBarrierType == LEFT || mBarrierType == RIGHT)
                    && (widget.getHorizontalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT)
                    && widget.mLeft.mTarget != null && widget.mRight.mTarget != null) {
                hasMatchConstraintWidgets = true;
                break;
            } else if ((mBarrierType == TOP || mBarrierType == BOTTOM)
                    && (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT)
                    && widget.mTop.mTarget != null && widget.mBottom.mTarget != null) {
                hasMatchConstraintWidgets = true;
                break;
            }
        }

        boolean mHasHorizontalCenteredDependents = mLeft.hasCenteredDependents() || mRight.hasCenteredDependents();
        boolean mHasVerticalCenteredDependents = mTop.hasCenteredDependents() || mBottom.hasCenteredDependents();
        boolean applyEqualityOnReferences = !hasMatchConstraintWidgets && ((mBarrierType == LEFT && mHasHorizontalCenteredDependents)
                                         || (mBarrierType == TOP && mHasVerticalCenteredDependents)
                                         || (mBarrierType == RIGHT && mHasHorizontalCenteredDependents)
                                         || (mBarrierType == BOTTOM && mHasVerticalCenteredDependents));

        int equalityOnReferencesStrength = SolverVariable.STRENGTH_EQUALITY;
        if (!applyEqualityOnReferences) {
            equalityOnReferencesStrength = SolverVariable.STRENGTH_HIGHEST;
        }
        for (int i = 0; i < mWidgetsCount; i++) {
            ConstraintWidget widget = mWidgets[i];
            if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
                continue;
            }
            SolverVariable target = system.createObjectVariable(widget.mListAnchors[mBarrierType]);
            widget.mListAnchors[mBarrierType].mSolverVariable = target;
            int margin = 0;
            if (widget.mListAnchors[mBarrierType].mTarget != null
                    && widget.mListAnchors[mBarrierType].mTarget.mOwner == this) {
                margin += widget.mListAnchors[mBarrierType].mMargin;
            }
            if (mBarrierType == LEFT || mBarrierType == TOP) {
                system.addLowerBarrier(position.mSolverVariable, target, mMargin - margin, hasMatchConstraintWidgets);
            } else {
                system.addGreaterBarrier(position.mSolverVariable, target, mMargin + margin, hasMatchConstraintWidgets);
            }
            if (USE_RELAX_GONE) {
                if (widget.getVisibility() != GONE || widget instanceof Guideline || widget instanceof Barrier) {
                    system.addEquality(position.mSolverVariable, target, mMargin + margin, equalityOnReferencesStrength);
                }
            } else {
                system.addEquality(position.mSolverVariable, target, mMargin + margin, equalityOnReferencesStrength);
            }
        }

        int barrierParentStrength = SolverVariable.STRENGTH_HIGHEST;
        int barrierParentStrengthOpposite = SolverVariable.STRENGTH_NONE;

        if (mBarrierType == LEFT) {
            system.addEquality(mRight.mSolverVariable, mLeft.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
            system.addEquality(mLeft.mSolverVariable, mParent.mRight.mSolverVariable, 0, barrierParentStrength);
            system.addEquality(mLeft.mSolverVariable, mParent.mLeft.mSolverVariable, 0, barrierParentStrengthOpposite);
        } else if (mBarrierType == RIGHT) {
            system.addEquality(mLeft.mSolverVariable, mRight.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
            system.addEquality(mLeft.mSolverVariable, mParent.mLeft.mSolverVariable, 0, barrierParentStrength);
            system.addEquality(mLeft.mSolverVariable, mParent.mRight.mSolverVariable, 0, barrierParentStrengthOpposite);
        } else if (mBarrierType == TOP) {
            system.addEquality(mBottom.mSolverVariable, mTop.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
            system.addEquality(mTop.mSolverVariable, mParent.mBottom.mSolverVariable, 0, barrierParentStrength);
            system.addEquality(mTop.mSolverVariable, mParent.mTop.mSolverVariable, 0, barrierParentStrengthOpposite);
        } else if (mBarrierType == BOTTOM) {
            system.addEquality(mTop.mSolverVariable, mBottom.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED);
            system.addEquality(mTop.mSolverVariable, mParent.mTop.mSolverVariable, 0, barrierParentStrength);
            system.addEquality(mTop.mSolverVariable, mParent.mBottom.mSolverVariable, 0, barrierParentStrengthOpposite);
        }
    }

    public void setMargin(int margin) {
        mMargin = margin;
    }

    public int getMargin() {
        return mMargin;
    }

    public int getOrientation() {
        switch (mBarrierType) {
            case LEFT:
            case RIGHT:
                return HORIZONTAL;
            case TOP:
            case BOTTOM:
                return VERTICAL;
        }
        return UNKNOWN;
    }

    public boolean allSolved() {
        if (!USE_RESOLUTION) {
            return false;
        }
        boolean hasAllWidgetsResolved = true;
        for (int i = 0; i < mWidgetsCount; i++) {
            ConstraintWidget widget = mWidgets[i];
            if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
                continue;
            }
            if ((mBarrierType == LEFT || mBarrierType == RIGHT) && !widget.isResolvedHorizontally()) {
                hasAllWidgetsResolved = false;
            } else if ((mBarrierType == TOP || mBarrierType == BOTTOM) && !widget.isResolvedVertically()) {
                hasAllWidgetsResolved = false;
            }
        }

        if (hasAllWidgetsResolved && mWidgetsCount > 0) {
            // we're done!
            int barrierPosition = 0;
            boolean initialized = false;
            for (int i = 0; i < mWidgetsCount; i++) {
                ConstraintWidget widget = mWidgets[i];
                if (!mAllowsGoneWidget && !widget.allowedInBarrier()) {
                    continue;
                }
                if (!initialized) {
                    if (mBarrierType == LEFT) {
                        barrierPosition = widget.getAnchor(ConstraintAnchor.Type.LEFT).getFinalValue();
                    } else if (mBarrierType == RIGHT) {
                        barrierPosition = widget.getAnchor(ConstraintAnchor.Type.RIGHT).getFinalValue();
                    } else if (mBarrierType == TOP) {
                        barrierPosition = widget.getAnchor(ConstraintAnchor.Type.TOP).getFinalValue();
                    } else if (mBarrierType == BOTTOM) {
                        barrierPosition = widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getFinalValue();
                    }
                    initialized = true;
                }
                if (mBarrierType == LEFT) {
                    barrierPosition = Math.min(barrierPosition, widget.getAnchor(ConstraintAnchor.Type.LEFT).getFinalValue());
                } else if (mBarrierType == RIGHT) {
                    barrierPosition = Math.max(barrierPosition, widget.getAnchor(ConstraintAnchor.Type.RIGHT).getFinalValue());
                } else if (mBarrierType == TOP) {
                    barrierPosition = Math.min(barrierPosition, widget.getAnchor(ConstraintAnchor.Type.TOP).getFinalValue());
                } else if (mBarrierType == BOTTOM) {
                    barrierPosition = Math.max(barrierPosition, widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getFinalValue());
                }
            }
            barrierPosition += mMargin;
            if (mBarrierType == LEFT || mBarrierType == RIGHT) {
                setFinalHorizontal(barrierPosition, barrierPosition);
            } else {
                setFinalVertical(barrierPosition, barrierPosition);
            }
            if (LinearSystem.FULL_DEBUG) {
                System.out.println("*** BARRIER " + getDebugName() + " SOLVED TO " + barrierPosition + " ***");
            }
            resolved = true;
            return true;
        }
        return false;
    }
}