All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.androidx.constraintlayout.core.widgets.Flow.kt Maven / Gradle / Ivy

The newest version!
/*
 * 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

import androidx.constraintlayout.core.LinearSystem
import androidx.constraintlayout.core.widgets.analyzer.BasicMeasure
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min

/**
 * Implements the Flow virtual layout.
 */
class Flow : VirtualLayout() {
    private var mHorizontalStyle = UNKNOWN
    private var mVerticalStyle = UNKNOWN
    private var mFirstHorizontalStyle = UNKNOWN
    private var mFirstVerticalStyle = UNKNOWN
    private var mLastHorizontalStyle = UNKNOWN
    private var mLastVerticalStyle = UNKNOWN
    private var mHorizontalBias = 0.5f
    private var mVerticalBias = 0.5f
    private var mFirstHorizontalBias = 0.5f
    private var mFirstVerticalBias = 0.5f
    private var mLastHorizontalBias = 0.5f
    private var mLastVerticalBias = 0.5f
    private var mHorizontalGap = 0
    private var mVerticalGap = 0
    private var mHorizontalAlign = HORIZONTAL_ALIGN_CENTER
    private var mVerticalAlign = VERTICAL_ALIGN_CENTER
    private var mWrapMode = WRAP_NONE
    private var mMaxElementsWrap = UNKNOWN
    private var mOrientation = HORIZONTAL
    private val mChainList = ArrayList()

    // Aligned management
    private var mAlignedBiggestElementsInRows: Array? = null
    private var mAlignedBiggestElementsInCols: Array? = null
    private var mAlignedDimensions: IntArray? = null
    private var mDisplayedWidgets: Array = emptyArray()
    private var mDisplayedWidgetsCount = 0
    override fun copy(src: ConstraintWidget, map: HashMap) {
        super.copy(src, map)
        val srcFLow = src as Flow
        mHorizontalStyle = srcFLow.mHorizontalStyle
        mVerticalStyle = srcFLow.mVerticalStyle
        mFirstHorizontalStyle = srcFLow.mFirstHorizontalStyle
        mFirstVerticalStyle = srcFLow.mFirstVerticalStyle
        mLastHorizontalStyle = srcFLow.mLastHorizontalStyle
        mLastVerticalStyle = srcFLow.mLastVerticalStyle
        mHorizontalBias = srcFLow.mHorizontalBias
        mVerticalBias = srcFLow.mVerticalBias
        mFirstHorizontalBias = srcFLow.mFirstHorizontalBias
        mFirstVerticalBias = srcFLow.mFirstVerticalBias
        mLastHorizontalBias = srcFLow.mLastHorizontalBias
        mLastVerticalBias = srcFLow.mLastVerticalBias
        mHorizontalGap = srcFLow.mHorizontalGap
        mVerticalGap = srcFLow.mVerticalGap
        mHorizontalAlign = srcFLow.mHorizontalAlign
        mVerticalAlign = srcFLow.mVerticalAlign
        mWrapMode = srcFLow.mWrapMode
        mMaxElementsWrap = srcFLow.mMaxElementsWrap
        mOrientation = srcFLow.mOrientation
    }

    /////////////////////////////////////////////////////////////////////////////////////////////
    // Accessors
    /////////////////////////////////////////////////////////////////////////////////////////////
    fun setOrientation(value: Int) {
        mOrientation = value
    }

    fun setFirstHorizontalStyle(value: Int) {
        mFirstHorizontalStyle = value
    }

    fun setFirstVerticalStyle(value: Int) {
        mFirstVerticalStyle = value
    }

    fun setLastHorizontalStyle(value: Int) {
        mLastHorizontalStyle = value
    }

    fun setLastVerticalStyle(value: Int) {
        mLastVerticalStyle = value
    }

    fun setHorizontalStyle(value: Int) {
        mHorizontalStyle = value
    }

    fun setVerticalStyle(value: Int) {
        mVerticalStyle = value
    }

    fun setHorizontalBias(value: Float) {
        mHorizontalBias = value
    }

    fun setVerticalBias(value: Float) {
        mVerticalBias = value
    }

    fun setFirstHorizontalBias(value: Float) {
        mFirstHorizontalBias = value
    }

    fun setFirstVerticalBias(value: Float) {
        mFirstVerticalBias = value
    }

    fun setLastHorizontalBias(value: Float) {
        mLastHorizontalBias = value
    }

    fun setLastVerticalBias(value: Float) {
        mLastVerticalBias = value
    }

    fun setHorizontalAlign(value: Int) {
        mHorizontalAlign = value
    }

    fun setVerticalAlign(value: Int) {
        mVerticalAlign = value
    }

    fun setWrapMode(value: Int) {
        mWrapMode = value
    }

    fun setHorizontalGap(value: Int) {
        mHorizontalGap = value
    }

    fun setVerticalGap(value: Int) {
        mVerticalGap = value
    }

    fun setMaxElementsWrap(value: Int) {
        mMaxElementsWrap = value
    }

    val maxElementsWrap: Float
        get() = mMaxElementsWrap.toFloat()

    /////////////////////////////////////////////////////////////////////////////////////////////
    // Utility methods
    /////////////////////////////////////////////////////////////////////////////////////////////
    private fun getWidgetWidth(widget: ConstraintWidget?, max: Int): Int {
        if (widget == null) {
            return 0
        }
        if (widget.horizontalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
            if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
                return 0
            } else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_PERCENT) {
                val value = (widget.mMatchConstraintPercentWidth * max).toInt()
                if (value != widget.width) {
                    widget.isMeasureRequested = true
                    measure(widget, DimensionBehaviour.FIXED, value, widget.verticalDimensionBehaviour, widget.height)
                }
                return value
            } else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) {
                return widget.width
            } else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) {
                return (widget.height * widget.dimensionRatio + 0.5f).toInt()
            }
        }
        return widget.width
    }

    private fun getWidgetHeight(widget: ConstraintWidget?, max: Int): Int {
        if (widget == null) {
            return 0
        }
        if (widget.verticalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
            if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
                return 0
            } else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_PERCENT) {
                val value = (widget.mMatchConstraintPercentHeight * max).toInt()
                if (value != widget.height) {
                    widget.isMeasureRequested = true
                    measure(widget, widget.horizontalDimensionBehaviour, widget.width, DimensionBehaviour.FIXED, value)
                }
                return value
            } else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) {
                return widget.height
            } else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) {
                return (widget.width * widget.dimensionRatio + 0.5f).toInt()
            }
        }
        return widget.height
    }

    /////////////////////////////////////////////////////////////////////////////////////////////
    // Measure
    /////////////////////////////////////////////////////////////////////////////////////////////
    override fun measure(widthMode: Int, widthSize: Int, heightMode: Int, heightSize: Int) {
        if (mWidgetsCount > 0 && !measureChildren()) {
            setMeasure(0, 0)
            needsCallbackFromSolver(false)
            return
        }
        var width = 0
        var height = 0
        val paddingLeft = paddingLeft
        val paddingRight = paddingRight
        val paddingTop = paddingTop
        val paddingBottom = paddingBottom
        val measured = IntArray(2)
        var max = widthSize - paddingLeft - paddingRight
        if (mOrientation == VERTICAL) {
            max = heightSize - paddingTop - paddingBottom
        }
        if (mOrientation == HORIZONTAL) {
            if (mHorizontalStyle == UNKNOWN) {
                mHorizontalStyle = CHAIN_SPREAD
            }
            if (mVerticalStyle == UNKNOWN) {
                mVerticalStyle = CHAIN_SPREAD
            }
        } else {
            if (mHorizontalStyle == UNKNOWN) {
                mHorizontalStyle = CHAIN_SPREAD
            }
            if (mVerticalStyle == UNKNOWN) {
                mVerticalStyle = CHAIN_SPREAD
            }
        }
        var widgets = mWidgets
        var gone = 0
        for (i in 0 until mWidgetsCount) {
            val widget = mWidgets[i]!!
            if (widget.visibility == GONE) {
                gone++
            }
        }
        var count = mWidgetsCount
        if (gone > 0) {
            widgets = arrayOfNulls(mWidgetsCount - gone)
            var j = 0
            for (i in 0 until mWidgetsCount) {
                val widget = mWidgets[i]!!
                if (widget.visibility != GONE) {
                    widgets[j] = widget
                    j++
                }
            }
            count = j
        }
        mDisplayedWidgets = widgets
        mDisplayedWidgetsCount = count
        when (mWrapMode) {
            WRAP_ALIGNED -> {
                measureAligned(widgets, count, mOrientation, max, measured)
            }
            WRAP_CHAIN -> {
                measureChainWrap(widgets, count, mOrientation, max, measured)
            }
            WRAP_NONE -> {
                measureNoWrap(widgets, count, mOrientation, max, measured)
            }
            WRAP_CHAIN_NEW -> {
                measureChainWrap_new(widgets, count, mOrientation, max, measured)
            }
        }
        width = measured[HORIZONTAL] + paddingLeft + paddingRight
        height = measured[VERTICAL] + paddingTop + paddingBottom
        var measuredWidth = 0
        var measuredHeight = 0
        if (widthMode == BasicMeasure.EXACTLY) {
            measuredWidth = widthSize
        } else if (widthMode == BasicMeasure.AT_MOST) {
            measuredWidth = min(width, widthSize)
        } else if (widthMode == BasicMeasure.UNSPECIFIED) {
            measuredWidth = width
        }
        if (heightMode == BasicMeasure.EXACTLY) {
            measuredHeight = heightSize
        } else if (heightMode == BasicMeasure.AT_MOST) {
            measuredHeight = min(height, heightSize)
        } else if (heightMode == BasicMeasure.UNSPECIFIED) {
            measuredHeight = height
        }
        setMeasure(measuredWidth, measuredHeight)
        width = measuredWidth
        height = measuredHeight
        needsCallbackFromSolver(mWidgetsCount > 0)
    }

    /////////////////////////////////////////////////////////////////////////////////////////////
    // Utility class representing a single chain
    /////////////////////////////////////////////////////////////////////////////////////////////
    private inner class WidgetsList(
        orientation: Int,
        left: ConstraintAnchor?, top: ConstraintAnchor,
        right: ConstraintAnchor?, bottom: ConstraintAnchor,
        max: Int
    ) {
        private var mOrientation = HORIZONTAL
        var biggest: ConstraintWidget? = null
        var biggestDimension = 0
        private var mLeft: ConstraintAnchor?
        private var mTop: ConstraintAnchor
        private var mRight: ConstraintAnchor?
        private var mBottom: ConstraintAnchor
        private var mPaddingLeft = 0
        private var mPaddingTop = 0
        private var mPaddingRight = 0
        private var mPaddingBottom = 0
        private var mWidth = 0
        private var mHeight = 0
        private var mStartIndex = 0
        private var mCount = 0
        private var mNbMatchConstraintsWidgets = 0
        private var mMax = 0
        fun setup(
            orientation: Int, left: ConstraintAnchor?, top: ConstraintAnchor,
            right: ConstraintAnchor?, bottom: ConstraintAnchor,
            paddingLeft: Int, paddingTop: Int, paddingRight: Int, paddingBottom: Int,
            max: Int
        ) {
            mOrientation = orientation
            mLeft = left
            mTop = top
            mRight = right
            mBottom = bottom
            mPaddingLeft = paddingLeft
            mPaddingTop = paddingTop
            mPaddingRight = paddingRight
            mPaddingBottom = paddingBottom
            mMax = max
        }

        fun clear() {
            biggestDimension = 0
            biggest = null
            mWidth = 0
            mHeight = 0
            mStartIndex = 0
            mCount = 0
            mNbMatchConstraintsWidgets = 0
        }

        fun setStartIndex(value: Int) {
            mStartIndex = value
        }

        fun getWidth(): Int {
            return if (mOrientation == HORIZONTAL) {
                mWidth - mHorizontalGap
            } else mWidth
        }

        fun getHeight(): Int {
            return if (mOrientation == VERTICAL) {
                mHeight - mVerticalGap
            } else mHeight
        }

        fun add(widget: ConstraintWidget?) {
            if (mOrientation == HORIZONTAL) {
                var width = getWidgetWidth(widget, mMax)
                if (widget!!.horizontalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
                    mNbMatchConstraintsWidgets++
                    width = 0
                }
                var gap = mHorizontalGap
                if (widget!!.visibility == GONE) {
                    gap = 0
                }
                mWidth += width + gap
                val height = getWidgetHeight(widget, mMax)
                if (biggest == null || biggestDimension < height) {
                    biggest = widget
                    biggestDimension = height
                    mHeight = height
                }
            } else {
                val width = getWidgetWidth(widget, mMax)
                var height = getWidgetHeight(widget, mMax)
                if (widget!!.verticalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
                    mNbMatchConstraintsWidgets++
                    height = 0
                }
                var gap = mVerticalGap
                if (widget!!.visibility == GONE) {
                    gap = 0
                }
                mHeight += height + gap
                if (biggest == null || biggestDimension < width) {
                    biggest = widget
                    biggestDimension = width
                    mWidth = width
                }
            }
            mCount++
        }

        fun createConstraints(isInRtl: Boolean, chainIndex: Int, isLastChain: Boolean) {
            val count = mCount
            for (i in 0 until count) {
                if (mStartIndex + i >= mDisplayedWidgetsCount) {
                    break
                }
                val widget = mDisplayedWidgets[mStartIndex + i]
                widget?.resetAnchors()
            }
            if (count == 0 || biggest == null) {
                return
            }
            val singleChain = isLastChain && chainIndex == 0
            var firstVisible = -1
            var lastVisible = -1
            for (i in 0 until count) {
                var index = i
                if (isInRtl) {
                    index = count - 1 - i
                }
                if (mStartIndex + index >= mDisplayedWidgetsCount) {
                    break
                }
                val widget = mDisplayedWidgets[mStartIndex + index]
                if (widget != null && widget.visibility == VISIBLE) {
                    if (firstVisible == -1) {
                        firstVisible = i
                    }
                    lastVisible = i
                }
            }
            var previous: ConstraintWidget? = null
            if (mOrientation == HORIZONTAL) {
                val verticalWidget: ConstraintWidget = biggest!!
                verticalWidget.verticalChainStyle = mVerticalStyle
                var padding = mPaddingTop
                if (chainIndex > 0) {
                    padding += mVerticalGap
                }
                verticalWidget.mTop.connect(mTop, padding)
                if (isLastChain) {
                    verticalWidget.mBottom.connect(mBottom, mPaddingBottom)
                }
                if (chainIndex > 0) {
                    val bottom = mTop.owner.mBottom
                    bottom.connect(verticalWidget.mTop, 0)
                }
                var baselineVerticalWidget: ConstraintWidget? = verticalWidget
                if (mVerticalAlign == VERTICAL_ALIGN_BASELINE && !verticalWidget.hasBaseline()) {
                    for (i in 0 until count) {
                        var index = i
                        if (isInRtl) {
                            index = count - 1 - i
                        }
                        if (mStartIndex + index >= mDisplayedWidgetsCount) {
                            break
                        }
                        val widget = mDisplayedWidgets[mStartIndex + index]
                        if (widget!!.hasBaseline()) {
                            baselineVerticalWidget = widget
                            break
                        }
                    }
                }
                for (i in 0 until count) {
                    var index = i
                    if (isInRtl) {
                        index = count - 1 - i
                    }
                    if (mStartIndex + index >= mDisplayedWidgetsCount) {
                        break
                    }
                    val widget = mDisplayedWidgets[mStartIndex + index] ?: continue
                    if (i == 0) {
                        widget.connect(widget.mLeft!!, mLeft!!, mPaddingLeft)
                    }

                    // ChainHead is always based on index, not i.
                    // E.g. RTL would have head at the right most widget.
                    if (index == 0) {
                        var style = mHorizontalStyle
                        var bias = if (isInRtl) 1 - mHorizontalBias else mHorizontalBias
                        if (mStartIndex == 0 && mFirstHorizontalStyle != UNKNOWN) {
                            style = mFirstHorizontalStyle
                            bias = if (isInRtl) 1 - mFirstHorizontalBias else mFirstHorizontalBias
                        } else if (isLastChain && mLastHorizontalStyle != UNKNOWN) {
                            style = mLastHorizontalStyle
                            bias = if (isInRtl) 1 - mLastHorizontalBias else mLastHorizontalBias
                        }
                        widget.horizontalChainStyle = style
                        widget.horizontalBiasPercent = bias
                    }
                    if (i == count - 1) {
                        widget.connect(widget.mRight!!, mRight!!, mPaddingRight)
                    }
                    if (previous != null) {
                        widget.mLeft!!.connect(previous.mRight, mHorizontalGap)
                        if (i == firstVisible) {
                            widget.mLeft!!.setGoneMargin(mPaddingLeft)
                        }
                        previous.mRight!!.connect(widget.mLeft, 0)
                        if (i == lastVisible + 1) {
                            previous.mRight!!.setGoneMargin(mPaddingRight)
                        }
                    }
                    if (widget !== verticalWidget) {
                        if (mVerticalAlign == VERTICAL_ALIGN_BASELINE && baselineVerticalWidget!!.hasBaseline()
                            && widget !== baselineVerticalWidget && widget.hasBaseline()
                        ) {
                            widget.mBaseline!!.connect(baselineVerticalWidget.mBaseline, 0)
                        } else {
                            when (mVerticalAlign) {
                                VERTICAL_ALIGN_TOP -> {
                                    widget.mTop.connect(verticalWidget.mTop, 0)
                                }
                                VERTICAL_ALIGN_BOTTOM -> {
                                    widget.mBottom.connect(verticalWidget.mBottom, 0)
                                }
                                VERTICAL_ALIGN_CENTER -> {
                                    if (singleChain) {
                                        widget.mTop.connect(mTop, mPaddingTop)
                                        widget.mBottom.connect(mBottom, mPaddingBottom)
                                    } else {
                                        widget.mTop.connect(verticalWidget.mTop, 0)
                                        widget.mBottom.connect(verticalWidget.mBottom, 0)
                                    }
                                }
                                else -> {
                                    if (singleChain) {
                                        widget.mTop.connect(mTop, mPaddingTop)
                                        widget.mBottom.connect(mBottom, mPaddingBottom)
                                    } else {
                                        widget.mTop.connect(verticalWidget.mTop, 0)
                                        widget.mBottom.connect(verticalWidget.mBottom, 0)
                                    }
                                }
                            }
                        }
                    }
                    previous = widget
                }
            } else {
                val horizontalWidget: ConstraintWidget = biggest!!
                horizontalWidget.horizontalChainStyle = mHorizontalStyle
                var padding = mPaddingLeft
                if (chainIndex > 0) {
                    padding += mHorizontalGap
                }
                if (isInRtl) {
                    horizontalWidget.mRight!!.connect(mRight, padding)
                    if (isLastChain) {
                        horizontalWidget.mLeft!!.connect(mLeft, mPaddingRight)
                    }
                    if (chainIndex > 0) {
                        val left = mRight!!.owner.mLeft
                        left!!.connect(horizontalWidget.mRight, 0)
                    }
                } else {
                    horizontalWidget.mLeft!!.connect(mLeft, padding)
                    if (isLastChain) {
                        horizontalWidget.mRight!!.connect(mRight, mPaddingRight)
                    }
                    if (chainIndex > 0) {
                        val right = mLeft!!.owner.mRight
                        right!!.connect(horizontalWidget.mLeft, 0)
                    }
                }
                for (i in 0 until count) {
                    if (mStartIndex + i >= mDisplayedWidgetsCount) {
                        break
                    }
                    val widget = mDisplayedWidgets[mStartIndex + i] ?: continue
                    if (i == 0) {
                        widget.connect(widget.mTop, mTop, mPaddingTop)
                        var style = mVerticalStyle
                        var bias = mVerticalBias
                        if (mStartIndex == 0 && mFirstVerticalStyle != UNKNOWN) {
                            style = mFirstVerticalStyle
                            bias = mFirstVerticalBias
                        } else if (isLastChain && mLastVerticalStyle != UNKNOWN) {
                            style = mLastVerticalStyle
                            bias = mLastVerticalBias
                        }
                        widget.verticalChainStyle = style
                        widget.verticalBiasPercent = bias
                    }
                    if (i == count - 1) {
                        widget.connect(widget.mBottom, mBottom, mPaddingBottom)
                    }
                    if (previous != null) {
                        widget.mTop.connect(previous.mBottom, mVerticalGap)
                        if (i == firstVisible) {
                            widget.mTop.setGoneMargin(mPaddingTop)
                        }
                        previous.mBottom.connect(widget.mTop, 0)
                        if (i == lastVisible + 1) {
                            previous.mBottom.setGoneMargin(mPaddingBottom)
                        }
                    }
                    if (widget !== horizontalWidget) {
                        if (isInRtl) {
                            when (mHorizontalAlign) {
                                HORIZONTAL_ALIGN_START -> {
                                    widget.mRight!!.connect(horizontalWidget.mRight, 0)
                                }
                                HORIZONTAL_ALIGN_CENTER -> {
                                    widget.mLeft!!.connect(horizontalWidget.mLeft, 0)
                                    widget.mRight!!.connect(horizontalWidget.mRight, 0)
                                }
                                HORIZONTAL_ALIGN_END -> {
                                    widget.mLeft!!.connect(horizontalWidget.mLeft, 0)
                                }
                            }
                        } else {
                            when (mHorizontalAlign) {
                                HORIZONTAL_ALIGN_START -> {
                                    widget.mLeft!!.connect(horizontalWidget.mLeft, 0)
                                }
                                HORIZONTAL_ALIGN_CENTER -> {
                                    if (singleChain) {
                                        widget.mLeft!!.connect(mLeft, mPaddingLeft)
                                        widget.mRight!!.connect(mRight, mPaddingRight)
                                    } else {
                                        widget.mLeft!!.connect(horizontalWidget.mLeft, 0)
                                        widget.mRight!!.connect(horizontalWidget.mRight, 0)
                                    }
                                }
                                HORIZONTAL_ALIGN_END -> {
                                    widget.mRight!!.connect(horizontalWidget.mRight, 0)
                                }
                            }
                        }
                    }
                    previous = widget
                }
            }
        }

        fun measureMatchConstraints(availableSpace: Int) {
            if (mNbMatchConstraintsWidgets == 0) {
                return
            }
            val count = mCount
            val widgetSize = availableSpace / mNbMatchConstraintsWidgets // that's completely incorrect and only works for spread with no weights?
            for (i in 0 until count) {
                if (mStartIndex + i >= mDisplayedWidgetsCount) {
                    break
                }
                val widget = mDisplayedWidgets[mStartIndex + i]
                if (mOrientation == HORIZONTAL) {
                    if (widget != null && widget.horizontalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
                        if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
                            measure(widget, DimensionBehaviour.FIXED, widgetSize, widget.verticalDimensionBehaviour, widget.height)
                        }
                    }
                } else {
                    if (widget != null && widget.verticalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
                        if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
                            measure(widget, widget.horizontalDimensionBehaviour, widget.width, DimensionBehaviour.FIXED, widgetSize)
                        }
                    }
                }
            }
            recomputeDimensions()
        }

        private fun recomputeDimensions() {
            mWidth = 0
            mHeight = 0
            biggest = null
            biggestDimension = 0
            val count = mCount
            for (i in 0 until count) {
                if (mStartIndex + i >= mDisplayedWidgetsCount) {
                    break
                }
                val widget = mDisplayedWidgets[mStartIndex + i]
                if (mOrientation == HORIZONTAL) {
                    val width = widget!!.width
                    var gap = mHorizontalGap
                    if (widget.visibility == GONE) {
                        gap = 0
                    }
                    mWidth += width + gap
                    val height = getWidgetHeight(widget, mMax)
                    if (biggest == null || biggestDimension < height) {
                        biggest = widget
                        biggestDimension = height
                        mHeight = height
                    }
                } else {
                    val width = getWidgetWidth(widget, mMax)
                    val height = getWidgetHeight(widget, mMax)
                    var gap = mVerticalGap
                    if (widget!!.visibility == GONE) {
                        gap = 0
                    }
                    mHeight += height + gap
                    if (biggest == null || biggestDimension < width) {
                        biggest = widget
                        biggestDimension = width
                        mWidth = width
                    }
                }
            }
        }

        init {
            mOrientation = orientation
            mLeft = left
            mTop = top
            mRight = right
            mBottom = bottom
            mPaddingLeft = paddingLeft
            mPaddingTop = paddingTop
            mPaddingRight = paddingRight
            mPaddingBottom = paddingBottom
            mMax = max
        }
    }
    /////////////////////////////////////////////////////////////////////////////////////////////
    // Measure Chain Wrap
    /////////////////////////////////////////////////////////////////////////////////////////////
    /**
     * Measure the virtual layout using a list of chains for the children
     * @param widgets     list of widgets
     * @param count
     * @param orientation the layout orientation (horizontal or vertical)
     * @param max         the maximum available space
     * @param measured    output parameters -- will contain the resulting measure
     */
    private fun measureChainWrap(widgets: Array, count: Int, orientation: Int, max: Int, measured: IntArray) {
        if (count == 0) {
            return
        }
        mChainList.clear()
        var list = WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max)
        mChainList.add(list)
        var nbMatchConstraintsWidgets = 0
        if (orientation == HORIZONTAL) {
            var width = 0
            for (i in 0 until count) {
                val widget = widgets[i]
                val w = getWidgetWidth(widget, max)
                if (widget!!.horizontalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
                    nbMatchConstraintsWidgets++
                }
                var doWrap = (width == max || width + mHorizontalGap + w > max) && list.biggest != null
                if (!doWrap && i > 0 && mMaxElementsWrap > 0 && i % mMaxElementsWrap == 0) {
                    doWrap = true
                }
                if (doWrap) {
                    width = w
                    list = WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max)
                    list.setStartIndex(i)
                    mChainList.add(list)
                } else {
                    if (i > 0) {
                        width += mHorizontalGap + w
                    } else {
                        width = w
                    }
                }
                list.add(widget)
            }
        } else {
            var height = 0
            for (i in 0 until count) {
                val widget = widgets[i]
                val h = getWidgetHeight(widget, max)
                if (widget!!.verticalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
                    nbMatchConstraintsWidgets++
                }
                var doWrap = (height == max || height + mVerticalGap + h > max) && list.biggest != null
                if (!doWrap && i > 0 && mMaxElementsWrap > 0 && i % mMaxElementsWrap == 0) {
                    doWrap = true
                }
                if (doWrap) {
                    height = h
                    list = WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max)
                    list.setStartIndex(i)
                    mChainList.add(list)
                } else {
                    if (i > 0) {
                        height += mVerticalGap + h
                    } else {
                        height = h
                    }
                }
                list.add(widget)
            }
        }
        val listCount = mChainList.size
        var left = mLeft
        var top = mTop
        var right = mRight
        var bottom = mBottom
        var paddingLeft = paddingLeft
        var paddingTop = paddingTop
        var paddingRight = paddingRight
        var paddingBottom = paddingBottom
        var maxWidth = 0
        var maxHeight = 0
        val needInternalMeasure = (horizontalDimensionBehaviour === DimensionBehaviour.WRAP_CONTENT
                || verticalDimensionBehaviour === DimensionBehaviour.WRAP_CONTENT)
        if (nbMatchConstraintsWidgets > 0 && needInternalMeasure) {
            // we have to remeasure them.
            for (i in 0 until listCount) {
                val current = mChainList[i]
                if (orientation == HORIZONTAL) {
                    current.measureMatchConstraints(max - current.getWidth())
                } else {
                    current.measureMatchConstraints(max - current.getHeight())
                }
            }
        }
        for (i in 0 until listCount) {
            val current = mChainList[i]
            if (orientation == HORIZONTAL) {
                if (i < listCount - 1) {
                    val next = mChainList[i + 1]
                    bottom = next.biggest!!.mTop
                    paddingBottom = 0
                } else {
                    bottom = mBottom
                    paddingBottom = this.paddingBottom
                }
                val currentBottom = current.biggest!!.mBottom
                current.setup(
                    orientation, left, top, right, bottom,
                    paddingLeft, paddingTop, paddingRight, paddingBottom, max
                )
                top = currentBottom
                paddingTop = 0
                maxWidth = max(maxWidth, current.getWidth())
                maxHeight += current.getHeight()
                if (i > 0) {
                    maxHeight += mVerticalGap
                }
            } else {
                if (i < listCount - 1) {
                    val next = mChainList[i + 1]
                    right = next.biggest!!.mLeft
                    paddingRight = 0
                } else {
                    right = mRight
                    paddingRight = this.paddingRight
                }
                val currentRight = current.biggest!!.mRight
                current.setup(
                    orientation, left, top, right, bottom,
                    paddingLeft, paddingTop, paddingRight, paddingBottom, max
                )
                left = currentRight
                paddingLeft = 0
                maxWidth += current.getWidth()
                maxHeight = max(maxHeight, current.getHeight())
                if (i > 0) {
                    maxWidth += mHorizontalGap
                }
            }
        }
        measured[HORIZONTAL] = maxWidth
        measured[VERTICAL] = maxHeight
    }
    /////////////////////////////////////////////////////////////////////////////////////////////
    // Measure Chain Wrap new
    /////////////////////////////////////////////////////////////////////////////////////////////
    /**
     * Measure the virtual layout using a list of chains for the children in new "fixed way"
     * @param widgets     list of widgets
     * @param count
     * @param orientation the layout orientation (horizontal or vertical)
     * @param max         the maximum available space
     * @param measured    output parameters -- will contain the resulting measure
     */
    private fun measureChainWrap_new(widgets: Array, count: Int, orientation: Int, max: Int, measured: IntArray) {
        if (count == 0) {
            return
        }
        mChainList.clear()
        var list = WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max)
        mChainList.add(list)
        var nbMatchConstraintsWidgets = 0
        if (orientation == HORIZONTAL) {
            var width = 0
            var col = 0
            for (i in 0 until count) {
                col++
                val widget = widgets[i]
                val w = getWidgetWidth(widget, max)
                if (widget!!.horizontalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
                    nbMatchConstraintsWidgets++
                }
                var doWrap = (width == max || width + mHorizontalGap + w > max) && list.biggest != null
                if (!doWrap && i > 0 && mMaxElementsWrap > 0 && col > mMaxElementsWrap) {
                    doWrap = true
                }
                if (doWrap) {
                    width = w
                    list = WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max)
                    list.setStartIndex(i)
                    mChainList.add(list)
                } else {
                    col = 0
                    if (i > 0) {
                        width += mHorizontalGap + w
                    } else {
                        width = w
                    }
                }
                list.add(widget)
            }
        } else {
            var height = 0
            var row = 0
            for (i in 0 until count) {
                val widget = widgets[i]
                val h = getWidgetHeight(widget, max)
                if (widget!!.verticalDimensionBehaviour === DimensionBehaviour.MATCH_CONSTRAINT) {
                    nbMatchConstraintsWidgets++
                }
                var doWrap = (height == max || height + mVerticalGap + h > max) && list.biggest != null
                if (!doWrap && i > 0 && mMaxElementsWrap > 0 && row > mMaxElementsWrap) {
                    doWrap = true
                }
                if (doWrap) {
                    height = h
                    list = WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max)
                    list.setStartIndex(i)
                    mChainList.add(list)
                } else {
                    row = 0
                    if (i > 0) {
                        height += mVerticalGap + h
                    } else {
                        height = h
                    }
                }
                list.add(widget)
            }
        }
        val listCount = mChainList.size
        var left = mLeft
        var top = mTop
        var right = mRight
        var bottom = mBottom
        var paddingLeft = paddingLeft
        var paddingTop = paddingTop
        var paddingRight = paddingRight
        var paddingBottom = paddingBottom
        var maxWidth = 0
        var maxHeight = 0
        val needInternalMeasure = (horizontalDimensionBehaviour === DimensionBehaviour.WRAP_CONTENT
                || verticalDimensionBehaviour === DimensionBehaviour.WRAP_CONTENT)
        if (nbMatchConstraintsWidgets > 0 && needInternalMeasure) {
            // we have to remeasure them.
            for (i in 0 until listCount) {
                val current = mChainList[i]
                if (orientation == HORIZONTAL) {
                    current.measureMatchConstraints(max - current.getWidth())
                } else {
                    current.measureMatchConstraints(max - current.getHeight())
                }
            }
        }
        for (i in 0 until listCount) {
            val current = mChainList[i]
            if (orientation == HORIZONTAL) {
                if (i < listCount - 1) {
                    val next = mChainList[i + 1]
                    bottom = next.biggest!!.mTop
                    paddingBottom = 0
                } else {
                    bottom = mBottom
                    paddingBottom = this.paddingBottom
                }
                val currentBottom = current.biggest!!.mBottom
                current.setup(
                    orientation, left, top, right, bottom,
                    paddingLeft, paddingTop, paddingRight, paddingBottom, max
                )
                top = currentBottom
                paddingTop = 0
                maxWidth = max(maxWidth, current.getWidth())
                maxHeight += current.getHeight()
                if (i > 0) {
                    maxHeight += mVerticalGap
                }
            } else {
                if (i < listCount - 1) {
                    val next = mChainList[i + 1]
                    right = next.biggest!!.mLeft
                    paddingRight = 0
                } else {
                    right = mRight
                    paddingRight = this.paddingRight
                }
                val currentRight = current.biggest!!.mRight
                current.setup(
                    orientation, left, top, right, bottom,
                    paddingLeft, paddingTop, paddingRight, paddingBottom, max
                )
                left = currentRight
                paddingLeft = 0
                maxWidth += current.getWidth()
                maxHeight = max(maxHeight, current.getHeight())
                if (i > 0) {
                    maxWidth += mHorizontalGap
                }
            }
        }
        measured[HORIZONTAL] = maxWidth
        measured[VERTICAL] = maxHeight
    }
    /////////////////////////////////////////////////////////////////////////////////////////////
    // Measure No Wrap
    /////////////////////////////////////////////////////////////////////////////////////////////
    /**
     * Measure the virtual layout using a single chain for the children
     * @param widgets     list of widgets
     * @param count
     * @param orientation the layout orientation (horizontal or vertical)
     * @param max         the maximum available space
     * @param measured    output parameters -- will contain the resulting measure
     */
    private fun measureNoWrap(widgets: Array, count: Int, orientation: Int, max: Int, measured: IntArray) {
        if (count == 0) {
            return
        }
        var list: WidgetsList? = null
        if (mChainList.size == 0) {
            list = WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max)
            mChainList.add(list)
        } else {
            list = mChainList[0]
            list.clear()
            list.setup(
                orientation, mLeft, mTop, mRight, mBottom,
                paddingLeft, paddingTop, paddingRight, paddingBottom, max
            )
        }
        for (i in 0 until count) {
            val widget = widgets[i]
            list.add(widget)
        }
        measured[HORIZONTAL] = list.getWidth()
        measured[VERTICAL] = list.getHeight()
    }
    /////////////////////////////////////////////////////////////////////////////////////////////
    // Measure Aligned
    /////////////////////////////////////////////////////////////////////////////////////////////
    /**
     * Measure the virtual layout arranging the children in a regular grid
     *
     * @param widgets     list of widgets
     * @param orientation the layout orientation (horizontal or vertical)
     * @param max         the maximum available space
     * @param measured    output parameters -- will contain the resulting measure
     */
    private fun measureAligned(widgets: Array, count: Int, orientation: Int, max: Int, measured: IntArray) {
        var done = false
        var rows = 0
        var cols = 0
        if (orientation == HORIZONTAL) {
            cols = mMaxElementsWrap
            if (cols <= 0) {
                // let's initialize cols with an acceptable value
                var w = 0
                cols = 0
                for (i in 0 until count) {
                    if (i > 0) {
                        w += mHorizontalGap
                    }
                    val widget = widgets[i] ?: continue
                    w += getWidgetWidth(widget, max)
                    if (w > max) {
                        break
                    }
                    cols++
                }
            }
        } else {
            rows = mMaxElementsWrap
            if (rows <= 0) {
                // let's initialize rows with an acceptable value
                var h = 0
                rows = 0
                for (i in 0 until count) {
                    if (i > 0) {
                        h += mVerticalGap
                    }
                    val widget = widgets[i] ?: continue
                    h += getWidgetHeight(widget, max)
                    if (h > max) {
                        break
                    }
                    rows++
                }
            }
        }
        if (mAlignedDimensions == null) {
            mAlignedDimensions = IntArray(2)
        }
        if (rows == 0 && orientation == VERTICAL
            || cols == 0 && orientation == HORIZONTAL
        ) {
            done = true
        }
        while (!done) {
            // get a num of rows (or cols)
            // get for each row and cols the chain of biggest elements
            if (orientation == HORIZONTAL) {
                rows = ceil((count / cols.toFloat()).toDouble()).toInt()
            } else {
                cols = ceil((count / rows.toFloat()).toDouble()).toInt()
            }
            if (mAlignedBiggestElementsInCols == null || mAlignedBiggestElementsInCols!!.size < cols) {
                mAlignedBiggestElementsInCols = arrayOfNulls(cols)
            } else {
                mAlignedBiggestElementsInCols?.fill(null)
            }
            if (mAlignedBiggestElementsInRows == null || mAlignedBiggestElementsInRows!!.size < rows) {
                mAlignedBiggestElementsInRows = arrayOfNulls(rows)
            } else {
                mAlignedBiggestElementsInRows?.fill(null)
            }
            for (i in 0 until cols) {
                for (j in 0 until rows) {
                    var index = j * cols + i
                    if (orientation == VERTICAL) {
                        index = i * rows + j
                    }
                    if (index >= widgets.size) {
                        continue
                    }
                    val widget = widgets[index] ?: continue
                    val w = getWidgetWidth(widget, max)
                    if (mAlignedBiggestElementsInCols!![i] == null
                        || mAlignedBiggestElementsInCols!![i]!!.width < w
                    ) {
                        mAlignedBiggestElementsInCols!![i] = widget
                    }
                    val h = getWidgetHeight(widget, max)
                    if (mAlignedBiggestElementsInRows!![j] == null
                        || mAlignedBiggestElementsInRows!![j]!!.height < h
                    ) {
                        mAlignedBiggestElementsInRows!![j] = widget
                    }
                }
            }
            var w = 0
            for (i in 0 until cols) {
                val widget = mAlignedBiggestElementsInCols!![i]
                if (widget != null) {
                    if (i > 0) {
                        w += mHorizontalGap
                    }
                    w += getWidgetWidth(widget, max)
                }
            }
            var h = 0
            for (j in 0 until rows) {
                val widget = mAlignedBiggestElementsInRows!![j]
                if (widget != null) {
                    if (j > 0) {
                        h += mVerticalGap
                    }
                    h += getWidgetHeight(widget, max)
                }
            }
            measured[HORIZONTAL] = w
            measured[VERTICAL] = h
            if (orientation == HORIZONTAL) {
                if (w > max) {
                    if (cols > 1) {
                        cols--
                    } else {
                        done = true
                    }
                } else {
                    done = true
                }
            } else { // VERTICAL
                if (h > max) {
                    if (rows > 1) {
                        rows--
                    } else {
                        done = true
                    }
                } else {
                    done = true
                }
            }
        }
        mAlignedDimensions!![HORIZONTAL] = cols
        mAlignedDimensions!![VERTICAL] = rows
    }

    private fun createAlignedConstraints(isInRtl: Boolean) {
        if (mAlignedDimensions == null || mAlignedBiggestElementsInCols == null || mAlignedBiggestElementsInRows == null) {
            return
        }
        for (i in 0 until mDisplayedWidgetsCount) {
            val widget = mDisplayedWidgets[i]
            widget!!.resetAnchors()
        }
        val cols = mAlignedDimensions!![HORIZONTAL]
        val rows = mAlignedDimensions!![VERTICAL]
        var previous: ConstraintWidget? = null
        var horizontalBias = mHorizontalBias
        for (i in 0 until cols) {
            var index = i
            if (isInRtl) {
                index = cols - i - 1
                horizontalBias = 1 - mHorizontalBias
            }
            val widget = mAlignedBiggestElementsInCols!![index]
            if (widget == null || widget.visibility == GONE) {
                continue
            }
            if (i == 0) {
                widget.connect(widget.mLeft!!, mLeft!!, paddingLeft)
                widget.horizontalChainStyle = mHorizontalStyle
                widget.horizontalBiasPercent = horizontalBias
            }
            if (i == cols - 1) {
                widget.connect(widget.mRight!!, mRight!!, paddingRight)
            }
            if (i > 0 && previous != null) {
                widget.connect(widget.mLeft!!, previous.mRight!!, mHorizontalGap)
                previous.connect(previous.mRight!!, widget.mLeft!!, 0)
            }
            previous = widget
        }
        for (j in 0 until rows) {
            val widget = mAlignedBiggestElementsInRows!![j]
            if (widget == null || widget.visibility == GONE) {
                continue
            }
            if (j == 0) {
                widget.connect(widget.mTop, mTop, paddingTop)
                widget.verticalChainStyle = mVerticalStyle
                widget.verticalBiasPercent = mVerticalBias
            }
            if (j == rows - 1) {
                widget.connect(widget.mBottom, mBottom, paddingBottom)
            }
            if (j > 0 && previous != null) {
                widget.connect(widget.mTop, previous.mBottom, mVerticalGap)
                previous.connect(previous.mBottom, widget.mTop, 0)
            }
            previous = widget
        }
        for (i in 0 until cols) {
            for (j in 0 until rows) {
                var index = j * cols + i
                if (mOrientation == VERTICAL) {
                    index = i * rows + j
                }
                if (index >= mDisplayedWidgets.size) {
                    continue
                }
                val widget = mDisplayedWidgets[index]
                if (widget == null || widget.visibility == GONE) {
                    continue
                }
                val biggestInCol = mAlignedBiggestElementsInCols!![i]
                val biggestInRow = mAlignedBiggestElementsInRows!![j]
                if (widget !== biggestInCol) {
                    widget.connect(widget.mLeft!!, biggestInCol!!.mLeft!!, 0)
                    widget.connect(widget.mRight!!, biggestInCol.mRight!!, 0)
                }
                if (widget !== biggestInRow) {
                    widget.connect(widget.mTop, biggestInRow!!.mTop, 0)
                    widget.connect(widget.mBottom, biggestInRow.mBottom, 0)
                }
            }
        }
    }
    /////////////////////////////////////////////////////////////////////////////////////////////
    // Add constraints to solver
    /////////////////////////////////////////////////////////////////////////////////////////////
    /**
     * Add this widget to the solver
     *
     * @param system the solver we want to add the widget to
     * @param optimize true if [Optimizer.OPTIMIZATION_GRAPH] is on
     */
    override fun addToSolver(system: LinearSystem, optimize: Boolean) {
        super.addToSolver(system, optimize)
        val isInRtl = parent != null && (parent as ConstraintWidgetContainer).isRtl
        when (mWrapMode) {
            WRAP_CHAIN -> {
                val count = mChainList.size
                var i = 0
                while (i < count) {
                    val list = mChainList[i]
                    list.createConstraints(isInRtl, i, i == count - 1)
                    i++
                }
            }
            WRAP_NONE -> {
                if (mChainList.size > 0) {
                    val list = mChainList[0]
                    list.createConstraints(isInRtl, 0, true)
                }
            }
            WRAP_ALIGNED -> {
                createAlignedConstraints(isInRtl)
            }
            WRAP_CHAIN_NEW -> {
                val count = mChainList.size
                var i = 0
                while (i < count) {
                    val list = mChainList[i]
                    list.createConstraints(isInRtl, i, i == count - 1)
                    i++
                }
            }
        }
        needsCallbackFromSolver(false)
    }

    companion object {
        const val HORIZONTAL_ALIGN_START = 0
        const val HORIZONTAL_ALIGN_END = 1
        const val HORIZONTAL_ALIGN_CENTER = 2
        const val VERTICAL_ALIGN_TOP = 0
        const val VERTICAL_ALIGN_BOTTOM = 1
        const val VERTICAL_ALIGN_CENTER = 2
        const val VERTICAL_ALIGN_BASELINE = 3
        const val WRAP_NONE = 0
        const val WRAP_CHAIN = 1
        const val WRAP_ALIGNED = 2
        const val WRAP_CHAIN_NEW = 3
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy