CurvedRow.kt
/*
* Copyright 2021 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.glance.wear.tiles.curved
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.unit.TextUnit
import androidx.glance.Emittable
import androidx.glance.EmittableWithChildren
import androidx.glance.GlanceModifier
import androidx.glance.GlanceNode
import androidx.glance.text.FontStyle
import androidx.glance.text.FontWeight
import androidx.glance.unit.ColorProvider
/**
* The alignment of a [CurvedRow]'s elements, with respect to its anchor angle. This specifies how
* elements added to a [CurvedRow] should be laid out with respect to the [CurvedRow]'s anchor
* angle.
*
* As an example, assume that the following diagrams are wrapped to an arc, and
* each represents a [CurvedRow] element containing a single text element. The text
* element's anchor angle is "0" for all cases.
*
* ```
* AnchorType.Start:
* -180 0 180
* Hello World!
*
*
* AnchorType.Center:
* -180 0 180
* Hello World!
*
* AnchorType.End:
* -180 0 180
* Hello World!
* ```
*/
@Suppress("INLINE_CLASS_DEPRECATED")
public inline class AnchorType private constructor(private val value: Int) {
public companion object {
/**
* Anchor at the start of the elements. This will cause elements added to a
* [CurvedRow] to begin at the given anchor angle, and sweep around to the right.
*/
public val Start: AnchorType = AnchorType(0)
/**
* Anchor at the center of the elements. This will cause the center of the
* whole set of elements added to a [CurvedRow] to be pinned at the given anchor angle.
*/
public val Center: AnchorType = AnchorType(1)
/**
* Anchor at the end of the elements. This will cause the set of elements
* inside the [CurvedRow] to end at the specified anchor angle, i.e. all elements
* should be to the left of anchor angle.
*/
public val End: AnchorType = AnchorType(2)
}
}
/**
* How to lay down components when they are thinner than the [CurvedRow]. Similar to vertical
* alignment in a Row.
*/
@Suppress("INLINE_CLASS_DEPRECATED")
public inline class RadialAlignment private constructor(private val value: Int) {
companion object {
/**
* Put the child closest to the center of the [CurvedRow], within the available space
*/
val Inner = RadialAlignment(0)
/**
* Put the child in the middle point of the available space.
*/
val Center = RadialAlignment(1)
/**
* Put the child farthest from the center of the [CurvedRow], within the available space
*/
val Outer = RadialAlignment(2)
}
}
internal class EmittableCurvedRow : EmittableWithChildren() {
override var modifier: GlanceModifier = GlanceModifier
var anchorDegrees: Float = 270f
var anchorType: AnchorType = AnchorType.Center
var radialAlignment: RadialAlignment = RadialAlignment.Center
}
internal class EmittableCurvedText : Emittable {
override var modifier: GlanceModifier = GlanceModifier
var text: String = ""
var textStyle: CurvedTextStyle? = null
}
/**
* A curved container. This container will fill itself to a circle, which fits inside its parent
* container, and all of its children will be placed on that circle. The parameters [anchorDegrees]
* and [anchorType] can be used to specify where to draw children within this circle. Each
* child will then be placed, one after the other, clockwise around the circle.
*
* While this container can hold any composable element, only those built specifically to work
* inside of a CurvedRow container (e.g. [CurvedRowScope.CurvedText]) will adapt themselves to the
* CurvedRow. Any other element will be drawn normally, at a tangent to the circle.
*
* @param modifier Modifiers for this container.
* @param anchorDegrees The angle for the anchor in degrees, used with [anchorType] to determine
* where to draw children. Note that 0 degrees is the 3 o'clock position on a device, and the
* angle sweeps clockwise. Values do not have to be clamped to the range 0-360; values less
* than 0 degrees will sweep anti-clockwise (i.e. -90 degrees is equivalent to 270 degrees),
* and values >360 will be be placed at X mod 360 degrees.
* @param anchorType Alignment of the contents of this container relative to [anchorDegrees].
* @param radialAlignment specifies where to lay down children that are thinner than the
* CurvedRow, either closer to the center (INNER), apart from the center (OUTER) or in the middle
* point (CENTER).
* @param content The content of this [CurvedRow].
*/
@Composable
public fun CurvedRow(
modifier: GlanceModifier = GlanceModifier,
anchorDegrees: Float = 270f,
anchorType: AnchorType = AnchorType.Center,
radialAlignment: RadialAlignment = RadialAlignment.Center,
content: @Composable CurvedRowScope.() -> Unit
) {
GlanceNode(
factory = ::EmittableCurvedRow,
update = {
this.set(modifier) { this.modifier = it }
this.set(anchorDegrees) { this.anchorDegrees = it }
this.set(anchorType) { this.anchorType = it }
this.set(radialAlignment) { this.radialAlignment = it }
},
content = { CurvedRowScope().content() }
)
}
/**
* Description of a text style for the [CurvedRowScope.CurvedText] composable.
*/
@Immutable
public class CurvedTextStyle(
public val color: ColorProvider? = null,
public val fontSize: TextUnit? = null,
public val fontWeight: FontWeight? = null,
public val fontStyle: FontStyle? = null
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is CurvedTextStyle) return false
if (color != other.color) return false
if (fontSize != other.fontSize) return false
if (fontWeight != other.fontWeight) return false
if (fontStyle != other.fontStyle) return false
return true
}
override fun hashCode(): Int {
var result = fontSize.hashCode()
result = 31 * result + fontWeight.hashCode()
result = 31 * result + fontStyle.hashCode()
return result
}
override fun toString() =
"TextStyle(size=$fontSize, fontWeight=$fontWeight, fontStyle=$fontStyle)"
}
/** A scope for elements which can only be contained within a [CurvedRow]. */
class CurvedRowScope {
/**
* A text element which will draw curved text. This is only valid as a direct descendant of a
* [CurvedRow]
*
* @param text The text to render.
* @param textStyle The style to use for the Text.
*/
@Composable
public fun CurvedText(
text: String,
modifier: GlanceModifier = GlanceModifier,
textStyle: CurvedTextStyle? = null
) {
GlanceNode(
factory = ::EmittableCurvedText,
update = {
this.set(text) { this.text = it }
this.set(modifier) { this.modifier = it }
this.set(textStyle) { this.textStyle = it }
}
)
}
}