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

net.peanuuutz.fork.ui.foundation.layout.Arrangement.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 The Android Open Source Project
 * Modifications Copyright 2022 Peanuuutz
 *
 * 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 net.peanuuutz.fork.ui.foundation.layout

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import net.peanuuutz.fork.ui.ui.layout.Alignment
import net.peanuuutz.fork.ui.ui.layout.Constraints.Companion.Unlimited
import net.peanuuutz.fork.util.common.fastForEachIndexed
import net.peanuuutz.fork.util.common.fastSum
import kotlin.math.roundToInt

object Arrangement {
    @Stable
    fun interface Horizontal {
        val spacing: Int
            get() = Unlimited

        fun arrangeHorizontally(
            contentSizes: IntArray,
            availableSpace: Int
        ): IntArray
    }

    @Stable
    fun interface Vertical {
        val spacing: Int
            get() = Unlimited

        fun arrangeVertically(
            contentSizes: IntArray,
            availableSpace: Int
        ): IntArray
    }

    @Stable
    fun interface HorizontalOrVertical : Horizontal, Vertical {
        fun arrange(
            contentSizes: IntArray,
            availableSpace: Int
        ): IntArray

        override val spacing: Int
            get() = Unlimited

        override fun arrangeHorizontally(contentSizes: IntArray, availableSpace: Int): IntArray {
            return arrange(contentSizes, availableSpace)
        }

        override fun arrangeVertically(contentSizes: IntArray, availableSpace: Int): IntArray {
            return arrange(contentSizes, availableSpace)
        }
    }

    @Stable
    val Left: Horizontal = object : Horizontal {
        override fun arrangeHorizontally(contentSizes: IntArray, availableSpace: Int): IntArray {
            if (contentSizes.isEmpty()) {
                return contentSizes
            }
            return arrange(contentSizes, 0.0f) { size -> size }
        }

        override fun toString(): String {
            return "Arrangement.Left"
        }
    }

    @Stable
    val Right: Horizontal = object : Horizontal {
        override fun arrangeHorizontally(contentSizes: IntArray, availableSpace: Int): IntArray {
            if (contentSizes.isEmpty()) {
                return contentSizes
            }
            if (availableSpace >= Unlimited) {
                return Left.arrangeHorizontally(contentSizes, availableSpace)
            }
            val initialPosition = (availableSpace - contentSizes.fastSum()).toFloat()
            return arrange(contentSizes, initialPosition) { size -> size }
        }

        override fun toString(): String {
            return "Arrangement.Right"
        }
    }

    @Stable
    val Top: Vertical = object : Vertical {
        override fun arrangeVertically(contentSizes: IntArray, availableSpace: Int): IntArray {
            if (contentSizes.isEmpty()) {
                return contentSizes
            }
            return arrange(contentSizes, 0.0f) { size -> size }
        }

        override fun toString(): String {
            return "Arrangement.Top"
        }
    }

    @Stable
    val Bottom: Vertical = object : Vertical {
        override fun arrangeVertically(contentSizes: IntArray, availableSpace: Int): IntArray {
            if (contentSizes.isEmpty()) {
                return contentSizes
            }
            if (availableSpace >= Unlimited) {
                return Top.arrangeVertically(contentSizes, availableSpace)
            }
            val initialPosition = (availableSpace - contentSizes.fastSum()).toFloat()
            return arrange(contentSizes, initialPosition) { size -> size }
        }

        override fun toString(): String {
            return "Arrangement.Bottom"
        }
    }

    @Stable
    val Start: HorizontalOrVertical = object : HorizontalOrVertical {
        override fun arrange(contentSizes: IntArray, availableSpace: Int): IntArray {
            return Top.arrangeVertically(contentSizes, availableSpace)
        }

        override fun toString(): String {
            return "Arrangement.Start"
        }
    }

    @Stable
    val End: HorizontalOrVertical = object : HorizontalOrVertical {
        override fun arrange(contentSizes: IntArray, availableSpace: Int): IntArray {
            return Bottom.arrangeVertically(contentSizes, availableSpace)
        }

        override fun toString(): String {
            return "Arrangement.End"
        }
    }

    @Stable
    val Center: HorizontalOrVertical = object : HorizontalOrVertical {
        override fun arrange(
            contentSizes: IntArray,
            availableSpace: Int
        ): IntArray {
            if (contentSizes.isEmpty()) {
                return contentSizes
            }
            if (availableSpace >= Unlimited) {
                return Top.arrangeVertically(contentSizes, availableSpace)
            }
            val initialPosition = (availableSpace - contentSizes.fastSum()).toFloat() / 2
            return arrange(contentSizes, initialPosition) { size -> size }
        }

        override fun toString(): String {
            return "Arrangement.Center"
        }
    }

    @Stable
    val SpaceEvenly: HorizontalOrVertical = object : HorizontalOrVertical {
        override fun arrange(
            contentSizes: IntArray,
            availableSpace: Int
        ): IntArray {
            if (contentSizes.isEmpty()) {
                return contentSizes
            }
            if (availableSpace >= Unlimited) {
                return Top.arrangeVertically(contentSizes, availableSpace)
            }
            val gap = (availableSpace - contentSizes.fastSum()).toFloat() / (contentSizes.size + 1)
            return arrange(contentSizes, gap) { size -> size + gap }
        }

        override fun toString(): String {
            return "Arrangement.SpaceEvenly"
        }
    }

    @Stable
    val SpaceBetween: HorizontalOrVertical = object : HorizontalOrVertical {
        override fun arrange(
            contentSizes: IntArray,
            availableSpace: Int
        ): IntArray {
            if (contentSizes.isEmpty()) {
                return contentSizes
            }
            if (availableSpace >= Unlimited) {
                return Top.arrangeVertically(contentSizes, availableSpace)
            }
            val gap = (availableSpace - contentSizes.fastSum()).toFloat() / (contentSizes.size - 1)
            return arrange(contentSizes, 0.0f) { size -> size + gap }
        }

        override fun toString(): String {
            return "Arrangement.SpaceBetween"
        }
    }

    @Stable
    val SpaceAround: HorizontalOrVertical = object : HorizontalOrVertical {
        override fun arrange(
            contentSizes: IntArray,
            availableSpace: Int
        ): IntArray {
            if (contentSizes.isEmpty()) {
                return contentSizes
            }
            if (availableSpace >= Unlimited) {
                return Top.arrangeVertically(contentSizes, availableSpace)
            }
            val singleGap = (availableSpace - contentSizes.fastSum()).toFloat() / (contentSizes.size * 2)
            return arrange(contentSizes, singleGap) { size -> size + singleGap * 2 }
        }

        override fun toString(): String {
            return "Arrangement.SpaceAround"
        }
    }

    @Stable
    fun spacedBy(
        space: Int
    ): HorizontalOrVertical {
        return SpacedAligned(space, Alignment.Top::alignVertically)
    }

    @Stable
    fun alignedBy(
        alignment: Alignment.Horizontal
    ): Horizontal {
        return SpacedAligned(0, alignment::alignHorizontally)
    }

    @Stable
    fun alignedBy(
        alignment: Alignment.Vertical
    ): Vertical {
        return SpacedAligned(0, alignment::alignVertically)
    }

    @Stable
    fun spacedAlignedBy(
        spacing: Int,
        alignment: Alignment.Horizontal
    ): Horizontal {
        return SpacedAligned(spacing, alignment::alignHorizontally)
    }

    @Stable
    fun spacedAlignedBy(
        spacing: Int,
        alignment: Alignment.Vertical
    ): Vertical {
        return SpacedAligned(spacing, alignment::alignVertically)
    }

    // ======== Internal ========

    @Immutable
    private data class SpacedAligned(
        override val spacing: Int,
        val alignment: (Int, Int) -> Int
    ) : HorizontalOrVertical {
        init {
            require(spacing < Unlimited) { "Cannot have infinite space" }
        }

        override fun arrange(
            contentSizes: IntArray,
            availableSpace: Int
        ): IntArray {
            if (contentSizes.isEmpty()) {
                return contentSizes
            }
            if (availableSpace >= Unlimited) {
                return Top.arrangeVertically(contentSizes, availableSpace)
            }
            val output = arrange(contentSizes, 0.0f) { size -> size + spacing }
            val totalSize = contentSizes.fastSum() + spacing * (contentSizes.size - 1)
            val offset = alignment(totalSize, availableSpace)
            for (i in output.indices) {
                output[i] += offset
            }
            return output
        }
    }
}

@Stable
val Arrangement.Horizontal.resolvedHorizontalSpacing: Int
    get() {
        val spacing = spacing
        return if (spacing >= Unlimited) 0 else spacing
    }

@Stable
val Arrangement.Vertical.resolvedVerticalSpacing: Int
    get() {
        val spacing = spacing
        return if (spacing >= Unlimited) 0 else spacing
    }

// ======== Internal ========

private inline fun arrange(
    contentSizes: IntArray,
    initialPosition: Float,
    positionDeltaProvider: (size: Float) -> Float
): IntArray {
    var currentPosition = initialPosition
    val result = IntArray(contentSizes.size)
    contentSizes.fastForEachIndexed { index, size ->
        val previousPosition = currentPosition
        currentPosition += positionDeltaProvider(size.toFloat())
        result[index] = previousPosition.roundToInt()
    }
    return result
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy