CurvedTextDelegate.android.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.wear.compose.foundation
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import kotlin.math.min
/**
* Used to cache computations and objects with expensive construction (Android's Paint & Path)
*/
internal actual class CurvedTextDelegate {
private var text: String = ""
private var clockwise: Boolean = true
private var fontSizePx: Float = 0f
private var arcPaddingPx: ArcPaddingPx = ArcPaddingPx(0f, 0f, 0f, 0f)
actual var textWidth by mutableStateOf(0f)
actual var textHeight by mutableStateOf(0f)
actual var baseLinePosition = 0f
private val paint = android.graphics.Paint().apply { isAntiAlias = true }
private val backgroundPath = android.graphics.Path()
private val textPath = android.graphics.Path()
var lastSize: Size? = null
actual fun updateIfNeeded(
text: String,
clockwise: Boolean,
fontSizePx: Float,
arcPaddingPx: ArcPaddingPx
) {
if (
text != this.text ||
clockwise != this.clockwise ||
fontSizePx != this.fontSizePx ||
arcPaddingPx != this.arcPaddingPx
) {
this.text = text
this.clockwise = clockwise
this.fontSizePx = fontSizePx
this.arcPaddingPx = arcPaddingPx
doUpdate()
lastSize = null // Ensure paths are recomputed
}
}
private fun doUpdate() {
paint.textSize = fontSizePx
val rect = android.graphics.Rect()
paint.getTextBounds(text, 0, text.length, rect)
textWidth = rect.width() + arcPaddingPx.before + arcPaddingPx.after
textHeight = -paint.fontMetrics.top + paint.fontMetrics.bottom +
arcPaddingPx.inner + arcPaddingPx.outer
baseLinePosition = arcPaddingPx.outer +
if (clockwise) -paint.fontMetrics.top else paint.fontMetrics.bottom
}
private fun updatePathsIfNeeded(size: Size) {
if (size != lastSize) {
lastSize = size
val clockwiseFactor = if (clockwise) 1f else -1f
val outerRadius = min(size.width, size.height) / 2f
val innerRadius = outerRadius - textHeight
val baselineRadius = outerRadius - baseLinePosition
val sweepDegree = (textWidth / baselineRadius)
.toDegrees()
.coerceAtMost(360f)
val paddingBeforeAsAngle = (arcPaddingPx.before / baselineRadius)
.toDegrees()
.coerceAtMost(360f)
val centerX = size.width / 2f
val centerY = size.height / 2f
backgroundPath.reset()
backgroundPath.arcTo(
centerX - outerRadius,
centerY - outerRadius,
centerX + outerRadius,
centerY + outerRadius,
anchor - clockwiseFactor * sweepDegree / 2,
clockwiseFactor * sweepDegree, false
)
backgroundPath.arcTo(
centerX - innerRadius,
centerY - innerRadius,
centerX + innerRadius,
centerY + innerRadius,
anchor + clockwiseFactor * sweepDegree / 2,
-clockwiseFactor * sweepDegree, false
)
backgroundPath.close()
textPath.reset()
textPath.addArc(
centerX - baselineRadius,
centerY - baselineRadius,
centerX + baselineRadius,
centerY + baselineRadius,
anchor - clockwiseFactor * (sweepDegree / 2 - paddingBeforeAsAngle),
clockwiseFactor * sweepDegree
)
}
}
actual fun doDraw(canvas: Canvas, size: Size, color: Color, background: Color) {
updatePathsIfNeeded(size)
if (background.isSpecified && background != Color.Transparent) {
paint.color = background.toArgb()
canvas.nativeCanvas.drawPath(backgroundPath, paint)
}
paint.color = color.toArgb()
canvas.nativeCanvas.drawTextOnPath(text, textPath, 0f, 0f, paint)
}
}
// We always draw curved text centered at the top, the CurvedRow will rotate us to the
// desired angle.
// Note that this is in the Angle system used by the arc drawing functions: 0 is 3 o clock,
// increasing clockwise.
private const val anchor = 270f