RunGroup.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.ConstraintWidgetContainer;

import java.util.ArrayList;

import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;

class RunGroup {
    public final static int START = 0;
    public final static int END = 1;
    public final static int BASELINE = 2;

    public static int index;

    public int position = 0;
    public boolean dual = false;

    WidgetRun firstRun = null;
    WidgetRun lastRun = null;
    ArrayList<WidgetRun> runs = new ArrayList<>();

    int groupIndex = 0;
    int direction;

    public RunGroup(WidgetRun run, int dir) {
        groupIndex = index;
        index++;
        firstRun = run;
        lastRun = run;
        direction = dir;
    }

    public void add(WidgetRun run) {
        runs.add(run);
        lastRun = run;
    }

    private long traverseStart(DependencyNode node, long startPosition) {
        WidgetRun run = node.run;
        if (run instanceof HelperReferences) {
            return startPosition;
        }
        long position = startPosition;

        // first, compute stuff dependent on this node.

        final int count = node.dependencies.size();
        for (int i = 0; i < count; i++) {
                Dependency dependency = node.dependencies.get(i);
                if (dependency instanceof DependencyNode) {
                    DependencyNode nextNode = (DependencyNode) dependency;
                    if (nextNode.run == run) {
                        // skip our own sibling node
                        continue;
                    }
                    position = Math.max(position, traverseStart(nextNode, startPosition + nextNode.margin));
                }
        }

        if (node == run.start) {
            // let's go for our sibling
            long dimension = run.getWrapDimension();
            position = Math.max(position, traverseStart(run.end, startPosition + dimension));
            position = Math.max(position, startPosition + dimension - run.end.margin);
        }

        return position;
    }

    private long traverseEnd(DependencyNode node, long startPosition) {
        WidgetRun run = node.run;
        if (run instanceof HelperReferences) {
            return startPosition;
        }
        long position = startPosition;

        // first, compute stuff dependent on this node.

        final int count = node.dependencies.size();
        for (int i = 0; i < count; i++) {
            Dependency dependency = node.dependencies.get(i);
            if (dependency instanceof DependencyNode) {
                DependencyNode nextNode = (DependencyNode) dependency;
                if (nextNode.run == run) {
                    // skip our own sibling node
                    continue;
                }
                position = Math.min(position, traverseEnd(nextNode, startPosition + nextNode.margin));
            }
        }

        if (node == run.end) {
            // let's go for our sibling
            long dimension = run.getWrapDimension();
            position = Math.min(position, traverseEnd(run.start, startPosition - dimension));
            position = Math.min(position, startPosition - dimension - run.start.margin);
        }

        return position;
    }

    public long computeWrapSize(ConstraintWidgetContainer container, int orientation) {
        if (firstRun instanceof ChainRun) {
            ChainRun chainRun = (ChainRun) firstRun;
            if (chainRun.orientation != orientation) {
                return 0;
            }
        } else {
            if (orientation == HORIZONTAL) {
                if (!(firstRun instanceof HorizontalWidgetRun)) {
                    return 0;
                }
            } else {
                if (!(firstRun instanceof VerticalWidgetRun)) {
                    return 0;
                }
            }
        }
        DependencyNode containerStart = orientation == HORIZONTAL ? container.horizontalRun.start : container.verticalRun.start;
        DependencyNode containerEnd = orientation == HORIZONTAL ? container.horizontalRun.end : container.verticalRun.end;

        boolean runWithStartTarget = firstRun.start.targets.contains(containerStart);
        boolean runWithEndTarget = firstRun.end.targets.contains(containerEnd);

        long dimension = firstRun.getWrapDimension();

        if (runWithStartTarget && runWithEndTarget) {
            long maxPosition = traverseStart(firstRun.start, 0);
            long minPosition = traverseEnd(firstRun.end, 0);

            // to compute the gaps, we subtract the margins
            long endGap = maxPosition - dimension;
            if (endGap >= -firstRun.end.margin) {
                endGap += firstRun.end.margin;
            }
            long startGap = -minPosition - dimension - firstRun.start.margin;
            if (startGap >= firstRun.start.margin) {
                startGap -= firstRun.start.margin;
            }
            float bias = firstRun.widget.getBiasPercent(orientation);
            long gap = 0;
            if (bias > 0) {
                gap = (long) ((startGap / bias) + (endGap / (1f - bias)));
            }

            startGap = (long) (0.5f + (gap * bias));
            endGap = (long) (0.5f + (gap * (1f - bias)));

            long runDimension = startGap + dimension + endGap;
            dimension = firstRun.start.margin + runDimension - firstRun.end.margin;

        } else if (runWithStartTarget) {
            long maxPosition = traverseStart(firstRun.start, firstRun.start.margin);
            long runDimension = firstRun.start.margin + dimension;
            dimension = Math.max(maxPosition, runDimension);
        } else if (runWithEndTarget) {
            long minPosition = traverseEnd(firstRun.end, firstRun.end.margin);
            long runDimension = -firstRun.end.margin + dimension;
            dimension = Math.max(-minPosition, runDimension);
        } else {
            dimension = firstRun.start.margin + firstRun.getWrapDimension() - firstRun.end.margin;
        }

        return dimension;
    }

    private boolean defineTerminalWidget(WidgetRun run, int orientation) {
        if (!run.widget.isTerminalWidget[orientation]) {
            return false;
        }
        for (Dependency dependency : run.start.dependencies) {
            if (dependency instanceof DependencyNode) {
                DependencyNode node = (DependencyNode) dependency;
                if (node.run == run) {
                    continue;
                }
                if (node == node.run.start) {
                    if (run instanceof ChainRun) {
                        ChainRun chainRun = (ChainRun) run;
                        for (WidgetRun widgetChainRun : chainRun.widgets) {
                            defineTerminalWidget(widgetChainRun, orientation);
                        }
                    } else {
                        if (!(run instanceof HelperReferences)) {
                            run.widget.isTerminalWidget[orientation] = false;
                        }
                    }
                    defineTerminalWidget(node.run, orientation);
                }
            }
        }
        for (Dependency dependency : run.end.dependencies) {
            if (dependency instanceof DependencyNode) {
                DependencyNode node = (DependencyNode) dependency;
                if (node.run == run) {
                    continue;
                }
                if (node == node.run.start){
                    if (run instanceof ChainRun) {
                        ChainRun chainRun = (ChainRun) run;
                        for (WidgetRun widgetChainRun : chainRun.widgets) {
                            defineTerminalWidget(widgetChainRun, orientation);
                        }
                    } else {
                        if (!(run instanceof HelperReferences)) {
                           run.widget.isTerminalWidget[orientation] = false;
                        }
                    }
                    defineTerminalWidget(node.run, orientation);
                }
            }
        }
        return false;
    }


    public void defineTerminalWidgets(boolean horizontalCheck, boolean verticalCheck) {
        if (horizontalCheck && firstRun instanceof HorizontalWidgetRun) {
            defineTerminalWidget(firstRun, HORIZONTAL);
        }
        if (verticalCheck && firstRun instanceof VerticalWidgetRun) {
            defineTerminalWidget(firstRun, VERTICAL);
        }
    }
}