CurvedBox.kt
/*
* Copyright 2023 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.wear.compose.foundation
import androidx.compose.foundation.layout.Box
import androidx.compose.ui.geometry.Offset
/**
* A layout composable that places its children on top of each other and on an arc. This is
* similar to a [Box] layout, but curved into a segment of an annulus.
*
* The thickness of the layout (the difference between the outer and inner radius) will be the
* same as the thickest child, and the angle taken will be the biggest angle of the
* children.
*
* Example usage:
* @sample androidx.wear.compose.foundation.samples.CurvedBoxSample
*
* @param modifier The [CurvedModifier] to apply to this curved row.
* @param radialAlignment Radial alignment specifies where to lay down children that are thinner
* than the CurvedBox, either closer to the center [CurvedAlignment.Radial.Inner], apart from
* the center [CurvedAlignment.Radial.Outer] or in the
* middle point [CurvedAlignment.Radial.Center]. If unspecified, they can choose for themselves.
* @param angularAlignment Angular alignment specifies where to lay down children that are thinner
* than the CurvedBox, either at the [CurvedAlignment.Angular.Start] of the layout,
* at the [CurvedAlignment.Angular.End], or [CurvedAlignment.Angular.Center].
* If unspecified or null, they can choose for themselves.
* @param contentBuilder Specifies the content of this layout, currently there are 5 available
* elements defined in foundation for this DSL: the sub-layouts [curvedBox], [curvedRow]
* and [curvedColumn], [basicCurvedText] and [curvedComposable]
* (used to add normal composables to curved layouts)
*/
public fun CurvedScope.curvedBox(
modifier: CurvedModifier = CurvedModifier,
radialAlignment: CurvedAlignment.Radial? = null,
angularAlignment: CurvedAlignment.Angular? = null,
contentBuilder: CurvedScope.() -> Unit
) = add(
CurvedBoxChild(
curvedLayoutDirection,
radialAlignment,
angularAlignment,
contentBuilder
),
modifier
)
internal class CurvedBoxChild(
curvedLayoutDirection: CurvedLayoutDirection,
private val radialAlignment: CurvedAlignment.Radial? = null,
private val angularAlignment: CurvedAlignment.Angular? = null,
contentBuilder: CurvedScope.() -> Unit
) : ContainerChild(curvedLayoutDirection, reverseLayout = false, contentBuilder) {
override fun doEstimateThickness(maxRadius: Float) =
children.maxOfOrNull { it.estimateThickness(maxRadius) } ?: 0f
override fun doRadialPosition(
parentOuterRadius: Float,
parentThickness: Float,
): PartialLayoutInfo {
// position children, take max sweep.
val maxSweep = children.maxOfOrNull { child ->
var childRadialPosition = parentOuterRadius
var childThickness = parentThickness
if (radialAlignment != null) {
childRadialPosition = parentOuterRadius - radialAlignment.ratio *
(parentThickness - child.estimatedThickness)
childThickness = child.estimatedThickness
}
child.radialPosition(
childRadialPosition,
childThickness
)
child.sweepRadians
} ?: 0f
return PartialLayoutInfo(
maxSweep,
parentOuterRadius,
parentThickness,
parentOuterRadius - parentThickness / 2
)
}
override fun doAngularPosition(
parentStartAngleRadians: Float,
parentSweepRadians: Float,
centerOffset: Offset
): Float {
children.forEach { child ->
var childAngularPosition = parentStartAngleRadians
var childSweep = parentSweepRadians
if (angularAlignment != null) {
childAngularPosition = parentStartAngleRadians + angularAlignment.ratio *
(parentSweepRadians - child.sweepRadians)
childSweep = child.sweepRadians
}
child.angularPosition(
childAngularPosition,
childSweep,
centerOffset
)
}
return parentStartAngleRadians
}
}