HorizontalWidgetRun.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 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.HORIZONTAL;
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.UNKNOWN;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
import static androidx.constraintlayout.core.widgets.analyzer.WidgetRun.RunType.CENTER;

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

public class HorizontalWidgetRun extends WidgetRun {

    private static int[] sTempDimensions = new int[2];

    public HorizontalWidgetRun(ConstraintWidget widget) {
        super(widget);
        start.mType = DependencyNode.Type.LEFT;
        end.mType = DependencyNode.Type.RIGHT;
        this.orientation = HORIZONTAL;
    }

    @Override
    public String toString() {
        return "HorizontalRun " + mWidget.getDebugName();
    }

    @Override
    void clear() {
        mRunGroup = null;
        start.clear();
        end.clear();
        mDimension.clear();
        mResolved = false;
    }

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

    @Override
    boolean supportsWrapComputation() {
        if (super.mDimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
            if (super.mWidget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
                return true;
            }
            return false;
        }
        return true;
    }

    @Override
    void apply() {
        if (mWidget.measured) {
            mDimension.resolve(mWidget.getWidth());
        }
        if (!mDimension.resolved) {
            super.mDimensionBehavior = mWidget.getHorizontalDimensionBehaviour();
            if (super.mDimensionBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
                if (mDimensionBehavior == MATCH_PARENT) {
                    ConstraintWidget parent = mWidget.getParent();
                    if (parent != null
                            && (parent.getHorizontalDimensionBehaviour() == FIXED
                            || parent.getHorizontalDimensionBehaviour() == MATCH_PARENT)) {
                        int resolvedDimension = parent.getWidth()
                                - mWidget.mLeft.getMargin() - mWidget.mRight.getMargin();
                        addTarget(start, parent.mHorizontalRun.start, mWidget.mLeft.getMargin());
                        addTarget(end, parent.mHorizontalRun.end, -mWidget.mRight.getMargin());
                        mDimension.resolve(resolvedDimension);
                        return;
                    }
                }
                if (mDimensionBehavior == FIXED) {
                    mDimension.resolve(mWidget.getWidth());
                }
            }
        } else {
            if (mDimensionBehavior == MATCH_PARENT) {
                ConstraintWidget parent = mWidget.getParent();
                if (parent != null
                        && (parent.getHorizontalDimensionBehaviour() == FIXED
                        || parent.getHorizontalDimensionBehaviour() == MATCH_PARENT)) {
                    addTarget(start, parent.mHorizontalRun.start, mWidget.mLeft.getMargin());
                    addTarget(end, parent.mHorizontalRun.end, -mWidget.mRight.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 (mDimension.resolved && mWidget.measured) {
            if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].mTarget != null
                    && mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].mTarget
                    != null) { // <-s-e->
                if (mWidget.isInHorizontalChain()) {
                    start.mMargin = mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin();
                    end.mMargin = -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin();
                } else {
                    DependencyNode startTarget =
                            getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT]);
                    if (startTarget != null) {
                        addTarget(start, startTarget,
                                mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin());
                    }
                    DependencyNode endTarget =
                            getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]);
                    if (endTarget != null) {
                        addTarget(end, endTarget,
                                -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin());
                    }
                    start.delegateToWidgetRun = true;
                    end.delegateToWidgetRun = true;
                }
            } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].mTarget
                    != null) { // <-s-e
                DependencyNode target =
                        getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT]);
                if (target != null) {
                    addTarget(start, target,
                            mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin());
                    addTarget(end, start, mDimension.value);
                }
            } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].mTarget
                    != null) {   //   s-e->
                DependencyNode target =
                        getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]);
                if (target != null) {
                    addTarget(end, target,
                            -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin());
                    addTarget(start, end, -mDimension.value);
                }
            } else {
                // no connections, nothing to do.
                if (!(mWidget instanceof Helper) && mWidget.getParent() != null
                        && mWidget.getAnchor(ConstraintAnchor.Type.CENTER).mTarget == null) {
                    DependencyNode left = mWidget.getParent().mHorizontalRun.start;
                    addTarget(start, left, mWidget.getX());
                    addTarget(end, start, mDimension.value);
                }
            }
        } else {
            if (mDimensionBehavior == MATCH_CONSTRAINT) {
                switch (mWidget.mMatchConstraintDefaultWidth) {
                    case MATCH_CONSTRAINT_RATIO: {
                        if (mWidget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO
                        ) {
                            // need to look into both side
                            start.updateDelegate = this;
                            end.updateDelegate = this;
                            mWidget.mVerticalRun.start.updateDelegate = this;
                            mWidget.mVerticalRun.end.updateDelegate = this;
                            mDimension.updateDelegate = this;

                            if (mWidget.isInVerticalChain()) {
                                mDimension.mTargets.add(mWidget.mVerticalRun.mDimension);
                                mWidget.mVerticalRun.mDimension.mDependencies.add(mDimension);
                                mWidget.mVerticalRun.mDimension.updateDelegate = this;
                                mDimension.mTargets.add(mWidget.mVerticalRun.start);
                                mDimension.mTargets.add(mWidget.mVerticalRun.end);
                                mWidget.mVerticalRun.start.mDependencies.add(mDimension);
                                mWidget.mVerticalRun.end.mDependencies.add(mDimension);
                            } else if (mWidget.isInHorizontalChain()) {
                                mWidget.mVerticalRun.mDimension.mTargets.add(mDimension);
                                mDimension.mDependencies.add(mWidget.mVerticalRun.mDimension);
                            } else {
                                mWidget.mVerticalRun.mDimension.mTargets.add(mDimension);
                            }
                            break;
                        }
                        // we have a ratio, but we depend on the other side computation
                        DependencyNode targetDimension = mWidget.mVerticalRun.mDimension;
                        mDimension.mTargets.add(targetDimension);
                        targetDimension.mDependencies.add(mDimension);
                        mWidget.mVerticalRun.start.mDependencies.add(mDimension);
                        mWidget.mVerticalRun.end.mDependencies.add(mDimension);
                        mDimension.delegateToWidgetRun = true;
                        mDimension.mDependencies.add(start);
                        mDimension.mDependencies.add(end);
                        start.mTargets.add(mDimension);
                        end.mTargets.add(mDimension);
                    }
                    break;
                    case MATCH_CONSTRAINT_PERCENT: {
                        // we need to look up the parent dimension
                        ConstraintWidget parent = mWidget.getParent();
                        if (parent == null) {
                            break;
                        }
                        DependencyNode targetDimension = parent.mVerticalRun.mDimension;
                        mDimension.mTargets.add(targetDimension);
                        targetDimension.mDependencies.add(mDimension);
                        mDimension.delegateToWidgetRun = true;
                        mDimension.mDependencies.add(start);
                        mDimension.mDependencies.add(end);
                    }
                    break;
                    case MATCH_CONSTRAINT_SPREAD: {
                        // the work is done in the update()
                    }
                    break;
                    default:
                        break;
                }
            }
            if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].mTarget != null
                    && mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].mTarget
                    != null) { // <-s-d-e->

                if (mWidget.isInHorizontalChain()) {
                    start.mMargin = mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin();
                    end.mMargin = -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin();
                } else {
                    DependencyNode startTarget =
                            getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT]);
                    DependencyNode endTarget =
                            getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]);
                    if (false) {
                        if (startTarget != null) {
                            addTarget(start, startTarget,
                                    mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin());
                        }
                        if (endTarget != null) {
                            addTarget(end, endTarget,
                                    -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]
                                            .getMargin());
                        }
                    } else {
                        if (startTarget != null) {
                            startTarget.addDependency(this);
                        }
                        if (endTarget != null) {
                            endTarget.addDependency(this);
                        }
                    }
                    mRunType = CENTER;
                }
            } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].mTarget
                    != null) { // <-s<-d<-e
                DependencyNode target =
                        getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT]);
                if (target != null) {
                    addTarget(start, target,
                            mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin());
                    addTarget(end, start, 1, mDimension);
                }
            } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].mTarget
                    != null) {   //   s->d->e->
                DependencyNode target =
                        getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]);
                if (target != null) {
                    addTarget(end, target,
                            -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin());
                    addTarget(start, end, -1, mDimension);
                }
            } else {
                // no connections, nothing to do.
                if (!(mWidget instanceof Helper) && mWidget.getParent() != null) {
                    DependencyNode left = mWidget.getParent().mHorizontalRun.start;
                    addTarget(start, left, mWidget.getX());
                    addTarget(end, start, 1, mDimension);
                }
            }
        }
    }

    private void computeInsetRatio(int[] dimensions,
            int x1,
            int x2,
            int y1,
            int y2,
            float ratio,
            int side) {
        int dx = x2 - x1;
        int dy = y2 - y1;
        switch (side) {
            case UNKNOWN: {
                int candidateX1 = (int) (0.5f + dy * ratio);
                int candidateY1 = dy;
                int candidateX2 = dx;
                int candidateY2 = (int) (0.5f + dx / ratio);
                if (candidateX1 <= dx && candidateY1 <= dy) {
                    dimensions[HORIZONTAL] = candidateX1;
                    dimensions[VERTICAL] = candidateY1;
                } else if (candidateX2 <= dx && candidateY2 <= dy) {
                    dimensions[HORIZONTAL] = candidateX2;
                    dimensions[VERTICAL] = candidateY2;
                }
            }
            break;
            case HORIZONTAL: {
                int horizontalSide = (int) (0.5f + dy * ratio);
                dimensions[HORIZONTAL] = horizontalSide;
                dimensions[VERTICAL] = dy;
            }
            break;
            case VERTICAL: {
                int verticalSide = (int) (0.5f + dx * ratio);
                dimensions[HORIZONTAL] = dx;
                dimensions[VERTICAL] = verticalSide;
            }
            break;
            default:
                break;
        }
    }

    @Override
    public void update(Dependency dependency) {
        switch (mRunType) {
            case START: {
                updateRunStart(dependency);
            }
            break;
            case END: {
                updateRunEnd(dependency);
            }
            break;
            case CENTER: {
                updateRunCenter(dependency, mWidget.mLeft, mWidget.mRight, HORIZONTAL);
                return;
            }
            default:
                break;
        }

        if (!mDimension.resolved) {
            if (mDimensionBehavior == MATCH_CONSTRAINT) {
                switch (mWidget.mMatchConstraintDefaultWidth) {
                    case MATCH_CONSTRAINT_RATIO: {
                        if (mWidget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD
                                || mWidget.mMatchConstraintDefaultHeight
                                == MATCH_CONSTRAINT_RATIO) {
                            DependencyNode secondStart = mWidget.mVerticalRun.start;
                            DependencyNode secondEnd = mWidget.mVerticalRun.end;
                            boolean s1 = mWidget.mLeft.mTarget != null;
                            boolean s2 = mWidget.mTop.mTarget != null;
                            boolean e1 = mWidget.mRight.mTarget != null;
                            boolean e2 = mWidget.mBottom.mTarget != null;

                            int definedSide = mWidget.getDimensionRatioSide();

                            if (s1 && s2 && e1 && e2) {
                                float ratio = mWidget.getDimensionRatio();
                                if (secondStart.resolved && secondEnd.resolved) {
                                    if (!(start.readyToSolve && end.readyToSolve)) {
                                        return;
                                    }
                                    int x1 = start.mTargets.get(0).value + start.mMargin;
                                    int x2 = end.mTargets.get(0).value - end.mMargin;
                                    int y1 = secondStart.value + secondStart.mMargin;
                                    int y2 = secondEnd.value - secondEnd.mMargin;
                                    computeInsetRatio(sTempDimensions,
                                            x1, x2, y1, y2, ratio, definedSide);
                                    mDimension.resolve(sTempDimensions[HORIZONTAL]);
                                    mWidget.mVerticalRun.mDimension
                                            .resolve(sTempDimensions[VERTICAL]);
                                    return;
                                }
                                if (start.resolved && end.resolved) {
                                    if (!(secondStart.readyToSolve && secondEnd.readyToSolve)) {
                                        return;
                                    }
                                    int x1 = start.value + start.mMargin;
                                    int x2 = end.value - end.mMargin;
                                    int y1 = secondStart.mTargets.get(0).value
                                            + secondStart.mMargin;
                                    int y2 = secondEnd.mTargets.get(0).value - secondEnd.mMargin;
                                    computeInsetRatio(sTempDimensions,
                                            x1, x2, y1, y2, ratio, definedSide);
                                    mDimension.resolve(sTempDimensions[HORIZONTAL]);
                                    mWidget.mVerticalRun.mDimension
                                            .resolve(sTempDimensions[VERTICAL]);
                                }
                                if (!(start.readyToSolve && end.readyToSolve
                                        && secondStart.readyToSolve
                                        && secondEnd.readyToSolve)) {
                                    return;
                                }
                                int x1 = start.mTargets.get(0).value + start.mMargin;
                                int x2 = end.mTargets.get(0).value - end.mMargin;
                                int y1 = secondStart.mTargets.get(0).value + secondStart.mMargin;
                                int y2 = secondEnd.mTargets.get(0).value - secondEnd.mMargin;
                                computeInsetRatio(sTempDimensions,
                                        x1, x2, y1, y2, ratio, definedSide);
                                mDimension.resolve(sTempDimensions[HORIZONTAL]);
                                mWidget.mVerticalRun.mDimension.resolve(sTempDimensions[VERTICAL]);
                            } else if (s1 && e1) {
                                if (!(start.readyToSolve && end.readyToSolve)) {
                                    return;
                                }
                                float ratio = mWidget.getDimensionRatio();
                                int x1 = start.mTargets.get(0).value + start.mMargin;
                                int x2 = end.mTargets.get(0).value - end.mMargin;

                                switch (definedSide) {
                                    case UNKNOWN:
                                    case HORIZONTAL: {
                                        int dx = x2 - x1;
                                        int ldx = getLimitedDimension(dx, HORIZONTAL);
                                        int dy = (int) (0.5f + ldx * ratio);
                                        int ldy = getLimitedDimension(dy, VERTICAL);
                                        if (dy != ldy) {
                                            ldx = (int) (0.5f + ldy / ratio);
                                        }
                                        mDimension.resolve(ldx);
                                        mWidget.mVerticalRun.mDimension.resolve(ldy);
                                    }
                                    break;
                                    case VERTICAL: {
                                        int dx = x2 - x1;
                                        int ldx = getLimitedDimension(dx, HORIZONTAL);
                                        int dy = (int) (0.5f + ldx / ratio);
                                        int ldy = getLimitedDimension(dy, VERTICAL);
                                        if (dy != ldy) {
                                            ldx = (int) (0.5f + ldy * ratio);
                                        }
                                        mDimension.resolve(ldx);
                                        mWidget.mVerticalRun.mDimension.resolve(ldy);
                                    }
                                    break;
                                    default:
                                        break;
                                }
                            } else if (s2 && e2) {
                                if (!(secondStart.readyToSolve && secondEnd.readyToSolve)) {
                                    return;
                                }
                                float ratio = mWidget.getDimensionRatio();
                                int y1 = secondStart.mTargets.get(0).value + secondStart.mMargin;
                                int y2 = secondEnd.mTargets.get(0).value - secondEnd.mMargin;

                                switch (definedSide) {
                                    case UNKNOWN:
                                    case VERTICAL: {
                                        int dy = y2 - y1;
                                        int ldy = getLimitedDimension(dy, VERTICAL);
                                        int dx = (int) (0.5f + ldy / ratio);
                                        int ldx = getLimitedDimension(dx, HORIZONTAL);
                                        if (dx != ldx) {
                                            ldy = (int) (0.5f + ldx * ratio);
                                        }
                                        mDimension.resolve(ldx);
                                        mWidget.mVerticalRun.mDimension.resolve(ldy);
                                    }
                                    break;
                                    case HORIZONTAL: {
                                        int dy = y2 - y1;
                                        int ldy = getLimitedDimension(dy, VERTICAL);
                                        int dx = (int) (0.5f + ldy * ratio);
                                        int ldx = getLimitedDimension(dx, HORIZONTAL);
                                        if (dx != ldx) {
                                            ldy = (int) (0.5f + ldx / ratio);
                                        }
                                        mDimension.resolve(ldx);
                                        mWidget.mVerticalRun.mDimension.resolve(ldy);
                                    }
                                    break;
                                    default:
                                        break;
                                }
                            }
                        } else {
                            int size = 0;
                            int ratioSide = mWidget.getDimensionRatioSide();
                            switch (ratioSide) {
                                case HORIZONTAL: {
                                    size = (int) (0.5f + mWidget.mVerticalRun.mDimension.value
                                            / mWidget.getDimensionRatio());
                                }
                                break;
                                case ConstraintWidget.VERTICAL: {
                                    size = (int) (0.5f + mWidget.mVerticalRun.mDimension.value
                                            * mWidget.getDimensionRatio());
                                }
                                break;
                                case ConstraintWidget.UNKNOWN: {
                                    size = (int) (0.5f + mWidget.mVerticalRun.mDimension.value
                                            * mWidget.getDimensionRatio());
                                }
                                break;
                                default:
                                    break;
                            }
                            mDimension.resolve(size);
                        }
                    }
                    break;
                    case MATCH_CONSTRAINT_PERCENT: {
                        ConstraintWidget parent = mWidget.getParent();
                        if (parent != null) {
                            if (parent.mHorizontalRun.mDimension.resolved) {
                                float percent = mWidget.mMatchConstraintPercentWidth;
                                int targetDimensionValue = parent.mHorizontalRun.mDimension.value;
                                int size = (int) (0.5f + targetDimensionValue * percent);
                                mDimension.resolve(size);
                            }
                        }
                    }
                    break;
                    default:
                        break;
                }
            }
        }

        if (!(start.readyToSolve && end.readyToSolve)) {
            return;
        }

        if (start.resolved && end.resolved && mDimension.resolved) {
            return;
        }

        if (!mDimension.resolved
                && mDimensionBehavior == MATCH_CONSTRAINT
                && mWidget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
                && !mWidget.isInHorizontalChain()) {

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

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

        if (!mDimension.resolved
                && mDimensionBehavior == MATCH_CONSTRAINT
                && matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
            if (start.mTargets.size() > 0 && end.mTargets.size() > 0) {
                DependencyNode startTarget = start.mTargets.get(0);
                DependencyNode endTarget = end.mTargets.get(0);
                int startPos = startTarget.value + start.mMargin;
                int endPos = endTarget.value + end.mMargin;
                int availableSpace = endPos - startPos;
                int value = Math.min(availableSpace, mDimension.wrapValue);
                int max = mWidget.mMatchConstraintMaxWidth;
                int min = mWidget.mMatchConstraintMinWidth;
                value = Math.max(min, value);
                if (max > 0) {
                    value = Math.min(max, value);
                }
                mDimension.resolve(value);
            }
        }

        if (!mDimension.resolved) {
            return;
        }
        // ready to solve, centering.
        DependencyNode startTarget = start.mTargets.get(0);
        DependencyNode endTarget = end.mTargets.get(0);
        int startPos = startTarget.value + start.mMargin;
        int endPos = endTarget.value + end.mMargin;
        float bias = mWidget.getHorizontalBiasPercent();
        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 - mDimension.value);
        start.resolve((int) (0.5f + startPos + distance * bias));
        end.resolve(start.value + mDimension.value);
    }

    // @TODO: add description
    @Override
    public void applyToWidget() {
        if (start.resolved) {
            mWidget.setX(start.value);
        }
    }

}