VerticalWidgetRun.java

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

import androidx.constraintlayout.core.widgets.ConstraintAnchor;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.Helper;

import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.FIXED;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_PARENT;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_PERCENT;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_RATIO;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
import static androidx.constraintlayout.core.widgets.analyzer.WidgetRun.RunType.CENTER;

public class VerticalWidgetRun extends WidgetRun {
    public DependencyNode baseline = new DependencyNode(this);
    androidx.constraintlayout.core.widgets.analyzer.DimensionDependency baselineDimension = null;

    public VerticalWidgetRun(ConstraintWidget widget) {
        super(widget);
        start.type = DependencyNode.Type.TOP;
        end.type = DependencyNode.Type.BOTTOM;
        baseline.type = DependencyNode.Type.BASELINE;
        this.orientation = VERTICAL;
    }

    @Override
    public String toString() {
        return "VerticalRun " + widget.getDebugName();
    }

    @Override
    void clear() {
        runGroup = null;
        start.clear();
        end.clear();
        baseline.clear();
        dimension.clear();
        resolved = false;
    }

    @Override
    void reset() {
        resolved = false;
        start.clear();
        start.resolved = false;
        end.clear();
        end.resolved = false;
        baseline.clear();
        baseline.resolved = false;
        dimension.resolved = false;
    }

    @Override
    boolean supportsWrapComputation() {
        if (super.dimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
            if (super.widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
                return true;
            }
            return false;
        }
        return true;
    }

    @Override
    public void update(Dependency dependency) {
        switch (mRunType) {
            case START: {
                updateRunStart(dependency);
            } break;
            case END: {
                updateRunEnd(dependency);
            } break;
            case CENTER: {
                updateRunCenter(dependency, widget.mTop, widget.mBottom, VERTICAL);
                return;
            }
        }
        if (true || dependency == dimension) {
            if (dimension.readyToSolve && !dimension.resolved) {
                if (dimensionBehavior == MATCH_CONSTRAINT) {
                    switch (widget.mMatchConstraintDefaultHeight) {
                        case MATCH_CONSTRAINT_RATIO: {
                            if (widget.horizontalRun.dimension.resolved) {
                                int size = 0;
                                int ratioSide = widget.getDimensionRatioSide();
                                switch (ratioSide) {
                                    case ConstraintWidget.HORIZONTAL: {
                                        size = (int) (0.5f + widget.horizontalRun.dimension.value * widget.getDimensionRatio());
                                    } break;
                                    case ConstraintWidget.VERTICAL: {
                                        size = (int) (0.5f + widget.horizontalRun.dimension.value / widget.getDimensionRatio());
                                    } break;
                                    case ConstraintWidget.UNKNOWN: {
                                        size = (int) (0.5f + widget.horizontalRun.dimension.value / widget.getDimensionRatio());
                                    } break;
                                }
                                dimension.resolve(size);
                            }
                        } break;
                        case MATCH_CONSTRAINT_PERCENT: {
                            ConstraintWidget parent = widget.getParent();
                            if (parent != null) {
                                if (parent.verticalRun.dimension.resolved) {
                                    float percent = widget.mMatchConstraintPercentHeight;
                                    int targetDimensionValue = parent.verticalRun.dimension.value;
                                    int size = (int) (0.5f + targetDimensionValue * percent);
                                    dimension.resolve(size);
                                }
                            }
                        } break;
                    }
                }
            }
        }
        if (!(start.readyToSolve && end.readyToSolve)) {
            return;
        }
        if (start.resolved && end.resolved && dimension.resolved) {
            return;
        }

        if (!dimension.resolved
                && dimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
                && widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
                && !widget.isInVerticalChain()) {

            DependencyNode startTarget = start.targets.get(0);
            DependencyNode endTarget = end.targets.get(0);
            int startPos = startTarget.value + start.margin;
            int endPos = endTarget.value + end.margin;

            int distance = endPos - startPos;
            start.resolve(startPos);
            end.resolve(endPos);
            dimension.resolve(distance);
            return;
        }

        if (!dimension.resolved
                && dimensionBehavior == MATCH_CONSTRAINT
                && matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
            if (start.targets.size() > 0 && end.targets.size() > 0) {
                DependencyNode startTarget = start.targets.get(0);
                DependencyNode endTarget = end.targets.get(0);
                int startPos = startTarget.value + start.margin;
                int endPos = endTarget.value + end.margin;
                int availableSpace = endPos - startPos;
                if (availableSpace < dimension.wrapValue) {
                    dimension.resolve(availableSpace);
                } else {
                    dimension.resolve(dimension.wrapValue);
                }
            }
        }

        if (!dimension.resolved) {
            return;
        }
        // ready to solve, centering.
        if (start.targets.size() > 0 && end.targets.size() > 0) {
            DependencyNode startTarget = start.targets.get(0);
            DependencyNode endTarget = end.targets.get(0);
            int startPos = startTarget.value + start.margin;
            int endPos = endTarget.value + end.margin;
            float bias = widget.getVerticalBiasPercent();
            if (startTarget == endTarget) {
                startPos = startTarget.value;
                endPos = endTarget.value;
                // TODO: this might be a nice feature to support, but I guess for now let's stay
                // compatible with 1.1
                bias = 0.5f;
            }
            int distance = (endPos - startPos - dimension.value);
            start.resolve((int) (0.5f + startPos + distance * bias));
            end.resolve(start.value + dimension.value);
        }
    }

    @Override
    void apply() {
        if (widget.measured) {
            dimension.resolve(widget.getHeight());
        }
        if (!dimension.resolved) {
            super.dimensionBehavior = widget.getVerticalDimensionBehaviour();
            if (widget.hasBaseline()) {
                baselineDimension = new BaselineDimensionDependency(this);
            }
            if (super.dimensionBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
                if (dimensionBehavior == MATCH_PARENT) {
                    ConstraintWidget parent = widget.getParent();
                    if (parent != null && parent.getVerticalDimensionBehaviour() == FIXED) {
                        int resolvedDimension = parent.getHeight() - widget.mTop.getMargin() - widget.mBottom.getMargin();
                        addTarget(start, parent.verticalRun.start, widget.mTop.getMargin());
                        addTarget(end, parent.verticalRun.end, -widget.mBottom.getMargin());
                        dimension.resolve(resolvedDimension);
                        return;
                    }
                }
                if (dimensionBehavior == FIXED) {
                    dimension.resolve(widget.getHeight());
                }
            }
        } else {
            if (dimensionBehavior == MATCH_PARENT) {
                ConstraintWidget parent = widget.getParent();
                if (parent != null && parent.getVerticalDimensionBehaviour() == FIXED) {
                    addTarget(start, parent.verticalRun.start, widget.mTop.getMargin());
                    addTarget(end, parent.verticalRun.end, -widget.mBottom.getMargin());
                    return;
                }
            }
        }
        // three basic possibilities:
        // <-s-e->
        // <-s-e
        //   s-e->
        // and a variation if the dimension is not yet known:
        // <-s-d-e->
        // <-s<-d<-e
        //   s->d->e->

        if (dimension.resolved && widget.measured) {
            if (widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null && widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget != null) { // <-s-e->
                if (widget.isInVerticalChain()) {
                    start.margin = widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin();
                    end.margin = -widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin();
                } else {
                    DependencyNode startTarget = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_TOP]);
                    if (startTarget != null) {
                        addTarget(start, startTarget, widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin());
                    }
                    DependencyNode endTarget = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]);
                    if (endTarget != null) {
                        addTarget(end, endTarget, -widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin());
                    }
                    start.delegateToWidgetRun = true;
                    end.delegateToWidgetRun = true;
                }
                if (widget.hasBaseline()) {
                    addTarget(baseline, start, widget.getBaselineDistance());
                }
            } else if (widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null) { // <-s-e
                DependencyNode target = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_TOP]);
                if (target != null) {
                    addTarget(start, target, widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin());
                    addTarget(end, start, dimension.value);
                    if (widget.hasBaseline()) {
                        addTarget(baseline, start, widget.getBaselineDistance());
                    }
                }
            } else if (widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget != null) {   //   s-e->
                DependencyNode target = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]);
                if (target != null) {
                    addTarget(end, target, -widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin());
                    addTarget(start, end, -dimension.value);
                }
                if (widget.hasBaseline()) {
                    addTarget(baseline, start, widget.getBaselineDistance());
                }
            } else if (widget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE].mTarget != null) {
                DependencyNode target = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE]);
                if (target != null) {
                    addTarget(baseline, target, 0);
                    addTarget(start, baseline, -widget.getBaselineDistance());
                    addTarget(end, start, dimension.value);
                }
            } else {
                // no connections, nothing to do.
                if (!(widget instanceof Helper) && widget.getParent() != null
                    && widget.getAnchor(ConstraintAnchor.Type.CENTER).mTarget == null) {
                    DependencyNode top = widget.getParent().verticalRun.start;
                    addTarget(start, top, widget.getY());
                    addTarget(end, start, dimension.value);
                    if (widget.hasBaseline()) {
                        addTarget(baseline, start, widget.getBaselineDistance());
                    }
                }
            }
        } else {
            if (!dimension.resolved && dimensionBehavior == MATCH_CONSTRAINT) {
                switch (widget.mMatchConstraintDefaultHeight) {
                    case MATCH_CONSTRAINT_RATIO: {
                        if (!widget.isInVerticalChain()) {
                            if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) {
                                // need to look into both side
                                // do nothing here -- let the HorizontalWidgetRun::update() deal with it.
                                break;
                            }
                            // we have a ratio, but we depend on the other side computation
                            DependencyNode targetDimension = widget.horizontalRun.dimension;
                            dimension.targets.add(targetDimension);
                            targetDimension.dependencies.add(dimension);
                            dimension.delegateToWidgetRun = true;
                            dimension.dependencies.add(start);
                            dimension.dependencies.add(end);
                        }
                    } break;
                    case MATCH_CONSTRAINT_PERCENT: {
                        // we need to look up the parent dimension
                        ConstraintWidget parent = widget.getParent();
                        if (parent == null) {
                            break;
                        }
                        DependencyNode targetDimension = parent.verticalRun.dimension;
                        dimension.targets.add(targetDimension);
                        targetDimension.dependencies.add(dimension);
                        dimension.delegateToWidgetRun = true;
                        dimension.dependencies.add(start);
                        dimension.dependencies.add(end);
                    } break;
                    case MATCH_CONSTRAINT_SPREAD: {
                        // the work is done in the update()
                    }
                }
            } else {
                dimension.addDependency(this);
            }
            if (widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null && widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget != null) { // <-s-d-e->
                if (widget.isInVerticalChain()) {
                    start.margin = widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin();
                    end.margin = -widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin();
                } else {
                    DependencyNode startTarget = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_TOP]);
                    DependencyNode endTarget = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]);
                    if (false) {
                        if (startTarget != null) {
                            addTarget(start, startTarget, widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin());
                        }
                        if (endTarget != null) {
                            addTarget(end, endTarget, -widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin());
                        }
                    } else {
                        if (startTarget != null) {
                            startTarget.addDependency(this);
                        }
                        if (endTarget != null) {
                            endTarget.addDependency(this);
                        }
                    }
                    mRunType = CENTER;
                }
                if (widget.hasBaseline()) {
                    addTarget(baseline, start, 1, baselineDimension);
                }
            } else if (widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null) { // <-s<-d<-e
                DependencyNode target = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_TOP]);
                if (target != null) {
                    addTarget(start, target, widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin());
                    addTarget(end, start, 1, dimension);
                    if (widget.hasBaseline()) {
                        addTarget(baseline, start, 1, baselineDimension);
                    }
                    if (dimensionBehavior == MATCH_CONSTRAINT) {
                        if (widget.getDimensionRatio() > 0) {
                            if (widget.horizontalRun.dimensionBehavior == MATCH_CONSTRAINT) {
                                widget.horizontalRun.dimension.dependencies.add(dimension);
                                dimension.targets.add(widget.horizontalRun.dimension);
                                dimension.updateDelegate = this;
                            }
                        }
                    }
                }
            } else if (widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget != null) {   //   s->d->e->
                DependencyNode target = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]);
                if (target != null) {
                    addTarget(end, target, -widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin());
                    addTarget(start, end, -1, dimension);
                    if (widget.hasBaseline()) {
                        addTarget(baseline, start, 1, baselineDimension);
                    }
                }
            } else if (widget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE].mTarget != null) {
                DependencyNode target = getTarget(widget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE]);
                if (target != null) {
                    addTarget(baseline, target, 0);
                    addTarget(start, baseline, -1, baselineDimension);
                    addTarget(end, start, 1, dimension);
                }
            } else {
                // no connections, nothing to do.
                if (!(widget instanceof Helper) && widget.getParent() != null) {
                    DependencyNode top = widget.getParent().verticalRun.start;
                    addTarget(start, top, widget.getY());
                    addTarget(end, start, 1, dimension);
                    if (widget.hasBaseline()) {
                        addTarget(baseline, start, 1, baselineDimension);
                    }
                    if (dimensionBehavior == MATCH_CONSTRAINT) {
                        if (widget.getDimensionRatio() > 0) {
                            if (widget.horizontalRun.dimensionBehavior == MATCH_CONSTRAINT) {
                                widget.horizontalRun.dimension.dependencies.add(dimension);
                                dimension.targets.add(widget.horizontalRun.dimension);
                                dimension.updateDelegate = this;
                            }
                        }
                    }
                }
            }

            // if dimension has no dependency, mark it as ready to solve
            if (dimension.targets.size() == 0) {
                dimension.readyToSolve = true;
            }
        }
    }

    public void applyToWidget() {
        if (start.resolved) {
            widget.setY(start.value);
        }
    }
}