From 838d775ab204d58430ebfe0eac22aea209d237be Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:51:49 -0600 Subject: [PATCH 1/2] chore(format): apply spotless ktlint formatting to maps3d-compose and maps3d-compose-demo --- .../maps3dcomposedemo/BaseVisualTest.kt | 4 +++- .../maps3dcomposedemo/Maps3DVisualTest.kt | 8 ++++++-- .../CameraAnimationsActivity.kt | 19 ++++++++++--------- .../MapInteractionsActivity.kt | 5 ++++- .../maps3dcomposedemo/widgets/TiltScale.kt | 10 ++++++++-- .../widgets/WhiskeyCompass.kt | 16 ++++++++++++---- .../maps/android/compose3d/GoogleMap3D.kt | 3 ++- .../android/compose3d/utils/CameraUpdate.kt | 3 ++- .../maps/android/compose3d/utils/Units.kt | 14 ++++++++++---- .../maps/android/compose3d/utils/Utilities.kt | 8 +++----- 10 files changed, 60 insertions(+), 30 deletions(-) diff --git a/maps3d-compose-demo/src/androidTest/java/com/example/maps3dcomposedemo/BaseVisualTest.kt b/maps3d-compose-demo/src/androidTest/java/com/example/maps3dcomposedemo/BaseVisualTest.kt index 4d4614b8..24eb2669 100644 --- a/maps3d-compose-demo/src/androidTest/java/com/example/maps3dcomposedemo/BaseVisualTest.kt +++ b/maps3d-compose-demo/src/androidTest/java/com/example/maps3dcomposedemo/BaseVisualTest.kt @@ -43,7 +43,9 @@ abstract class BaseVisualTest { key } - protected fun captureScreenshot(filename: String = "screenshot_${System.currentTimeMillis()}.png"): Bitmap { + protected fun captureScreenshot( + filename: String = "screenshot_${System.currentTimeMillis()}.png", + ): Bitmap { val screenshotFile = File(context.getExternalFilesDir(null), filename) val screenshotTaken = uiDevice.takeScreenshot(screenshotFile) assertTrue("Failed to take screenshot: $filename", screenshotTaken) diff --git a/maps3d-compose-demo/src/androidTest/java/com/example/maps3dcomposedemo/Maps3DVisualTest.kt b/maps3d-compose-demo/src/androidTest/java/com/example/maps3dcomposedemo/Maps3DVisualTest.kt index 86eae31d..8949aa3e 100644 --- a/maps3d-compose-demo/src/androidTest/java/com/example/maps3dcomposedemo/Maps3DVisualTest.kt +++ b/maps3d-compose-demo/src/androidTest/java/com/example/maps3dcomposedemo/Maps3DVisualTest.kt @@ -408,7 +408,9 @@ class Maps3DVisualTest : BaseVisualTest() { // Parse JSON and click try { - val jsonStr = geminiResponse?.substringAfter("{")?.substringBeforeLast("}")?.let { "{$it}" } ?: "" + val jsonStr = + geminiResponse?.substringAfter("{")?.substringBeforeLast("}")?.let { "{$it}" } + ?: "" val json = org.json.JSONObject(jsonStr) val x = json.getDouble("x") val y = json.getDouble("y") @@ -418,7 +420,9 @@ class Maps3DVisualTest : BaseVisualTest() { uiDevice.click(clickX, clickY) } catch (e: Exception) { - org.junit.Assert.fail("Failed to parse coordinates from Gemini response: $geminiResponse. Error: ${e.message}") + org.junit.Assert.fail( + "Failed to parse coordinates from Gemini response: $geminiResponse. Error: ${e.message}", + ) } // Wait for UI to update diff --git a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/CameraAnimationsActivity.kt b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/CameraAnimationsActivity.kt index 64a53fe0..1da9a13d 100644 --- a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/CameraAnimationsActivity.kt +++ b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/CameraAnimationsActivity.kt @@ -176,15 +176,16 @@ fun CameraAnimationsScreen() { } // Helper extensions -suspend fun com.google.android.gms.maps3d.GoogleMap3D.awaitCameraAnimation() = suspendCancellableCoroutine { continuation -> - setCameraAnimationEndListener { - setCameraAnimationEndListener(null) // Cleanup - if (continuation.isActive) { - continuation.resume(Unit) +suspend fun com.google.android.gms.maps3d.GoogleMap3D.awaitCameraAnimation() = + suspendCancellableCoroutine { continuation -> + setCameraAnimationEndListener { + setCameraAnimationEndListener(null) // Cleanup + if (continuation.isActive) { + continuation.resume(Unit) + } } - } - continuation.invokeOnCancellation { - setCameraAnimationEndListener(null) + continuation.invokeOnCancellation { + setCameraAnimationEndListener(null) + } } -} diff --git a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/MapInteractionsActivity.kt b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/MapInteractionsActivity.kt index e47f8341..cd6c4ef7 100644 --- a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/MapInteractionsActivity.kt +++ b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/MapInteractionsActivity.kt @@ -95,7 +95,10 @@ fun MapInteractionsScreen() { map3dInstance = instance // Set up click listener directly on the instance instance.setMap3DClickListener { location, placeId -> - Log.d("MapInteractionsActivity", "Map clicked at ${location.latitude}, ${location.longitude}") + Log.d( + "MapInteractionsActivity", + "Map clicked at ${location.latitude}, ${location.longitude}", + ) clickedInfo = if (placeId != null) { "Clicked Place ID: $placeId\nAt: ${location.latitude}, ${location.longitude}" } else { diff --git a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/TiltScale.kt b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/TiltScale.kt index 358e5344..babcb15e 100644 --- a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/TiltScale.kt +++ b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/TiltScale.kt @@ -68,10 +68,16 @@ fun TiltScale(tilt: Float, modifier: Modifier = Modifier) { ) if (isMajor) { - val measuredText = textMeasurer.measure(i.toString(), style = TextStyle(color = onPrimaryColor, fontSize = 12.sp)) + val measuredText = textMeasurer.measure( + i.toString(), + style = TextStyle(color = onPrimaryColor, fontSize = 12.sp), + ) drawText( textLayoutResult = measuredText, - topLeft = Offset(centerLineX + 20f, yPos - measuredText.size.height / 2f), + topLeft = Offset( + centerLineX + 20f, + yPos - measuredText.size.height / 2f, + ), ) } } diff --git a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/WhiskeyCompass.kt b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/WhiskeyCompass.kt index d969d546..aaf0e4fc 100644 --- a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/WhiskeyCompass.kt +++ b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/WhiskeyCompass.kt @@ -73,7 +73,9 @@ fun WhiskeyCompass( showDegreeLabels: Boolean = true, degreeLabelInterval: Int = 15, - degreeLabelTextStyle: TextStyle = MaterialTheme.typography.labelSmall.copy(textAlign = TextAlign.Center), + degreeLabelTextStyle: TextStyle = MaterialTheme.typography.labelSmall.copy( + textAlign = TextAlign.Center, + ), degreeLabelVerticalOffset: Dp = 4.dp, showCardinalLabels: Boolean = true, @@ -142,7 +144,9 @@ fun WhiskeyCompass( val absoluteDegree = repetitionBaseDegree + degreeInRepetition val xPos = absoluteDegree * pixelsPerDegree - if (xPos < -xOffset + canvasWidth + canvasWidth && xPos > -xOffset - canvasWidth) { + if (xPos < -xOffset + canvasWidth + canvasWidth && + xPos > -xOffset - canvasWidth + ) { val isMajorTickEquivalent = degreeInRepetition % 10 == 0 val isMinorTickEquivalent = degreeInRepetition % 5 == 0 && !isMajorTickEquivalent @@ -167,7 +171,9 @@ fun WhiskeyCompass( textLayoutResult = measuredText, topLeft = Offset( x = xPos - measuredText.size.width / 2f, - y = tickTopY - measuredText.size.height - cardinalLabelVerticalOffsetPx, + y = + tickTopY - measuredText.size.height - + cardinalLabelVerticalOffsetPx, ), ) } @@ -272,7 +278,9 @@ private fun FlatWhiskeyCompassPreview() { lubberLineColor = Color(0xFFFFD600), pixelsPerDegree = 12f, degreeLabelInterval = 10, - degreeLabelTextStyle = MaterialTheme.typography.bodySmall.copy(color = Color(0xFF81D4FA)), + degreeLabelTextStyle = MaterialTheme.typography.bodySmall.copy( + color = Color(0xFF81D4FA), + ), cardinalLabelTextStyle = MaterialTheme.typography.labelLarge.copy( color = Color.White, fontWeight = FontWeight.Bold, diff --git a/maps3d-compose/src/main/java/com/google/maps/android/compose3d/GoogleMap3D.kt b/maps3d-compose/src/main/java/com/google/maps/android/compose3d/GoogleMap3D.kt index 4ec383c0..c01196fc 100644 --- a/maps3d-compose/src/main/java/com/google/maps/android/compose3d/GoogleMap3D.kt +++ b/maps3d-compose/src/main/java/com/google/maps/android/compose3d/GoogleMap3D.kt @@ -83,7 +83,8 @@ fun GoogleMap3D( minTilt = 0.0, maxTilt = 90.0, bounds = null, - mapMode = Map3DMode.SATELLITE, // using the class from com.google.android.gms.maps3d.model.Map3DMode + // Using Map3DMode constant + mapMode = Map3DMode.SATELLITE, mapId = null, minAltitude = 0.0, maxAltitude = 1000000.0, diff --git a/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/CameraUpdate.kt b/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/CameraUpdate.kt index 00998765..adc56ee6 100644 --- a/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/CameraUpdate.kt +++ b/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/CameraUpdate.kt @@ -63,7 +63,8 @@ sealed class CameraUpdate { fun FlyToOptions.toCameraUpdate(): CameraUpdate = CameraUpdate.FlyTo(this.toValidFlyToOptions()) -fun FlyAroundOptions.toCameraUpdate(): CameraUpdate = CameraUpdate.FlyAround(this.toValidFlyAroundOptions()) +fun FlyAroundOptions.toCameraUpdate(): CameraUpdate = + CameraUpdate.FlyAround(this.toValidFlyAroundOptions()) fun FlyToOptions.toValidFlyToOptions(): FlyToOptions = this.copy( endCamera = this.endCamera.toValidCamera(), diff --git a/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/Units.kt b/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/Units.kt index d0d96c02..00a478bf 100644 --- a/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/Units.kt +++ b/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/Units.kt @@ -137,24 +137,30 @@ fun getUnitsConverter(countryCode: String?): UnitsConverter { /** Class to render measurements in imperial units. */ object ImperialUnitsConverter : UnitsConverter() { - override fun toDistanceUnits(meters: Meters): ValueWithUnitsTemplate = if (meters < 0.25.miles) { + override fun toDistanceUnits(meters: Meters): ValueWithUnitsTemplate = if (meters < + 0.25.miles + ) { ValueWithUnitsTemplate(meters.toFeet, R.string.in_feet) } else { ValueWithUnitsTemplate(meters.toMiles, R.string.in_miles) } - override fun toElevationUnits(meters: Meters): ValueWithUnitsTemplate = ValueWithUnitsTemplate(meters.toFeet, R.string.in_feet) + override fun toElevationUnits(meters: Meters): ValueWithUnitsTemplate = + ValueWithUnitsTemplate(meters.toFeet, R.string.in_feet) } /** Class to render measurements in metric units. */ object MetricUnitsConverter : UnitsConverter() { - override fun toDistanceUnits(meters: Meters): ValueWithUnitsTemplate = if (meters < 1000.meters) { + override fun toDistanceUnits(meters: Meters): ValueWithUnitsTemplate = if (meters < + 1000.meters + ) { ValueWithUnitsTemplate(meters.toMeters, R.string.in_meters) } else { ValueWithUnitsTemplate(meters.toKilometers, R.string.in_kilometers) } - override fun toElevationUnits(meters: Meters): ValueWithUnitsTemplate = ValueWithUnitsTemplate(meters.toMeters, R.string.in_meters) + override fun toElevationUnits(meters: Meters): ValueWithUnitsTemplate = + ValueWithUnitsTemplate(meters.toMeters, R.string.in_meters) } /** A composition local that provides a [UnitsConverter] instance. */ diff --git a/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/Utilities.kt b/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/Utilities.kt index 951e8f1a..1f887fbb 100644 --- a/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/Utilities.kt +++ b/maps3d-compose/src/main/java/com/google/maps/android/compose3d/utils/Utilities.kt @@ -157,7 +157,8 @@ fun LatLngAltitude.toValidLocation(): LatLngAltitude { * @receiver The Number? to convert. * @return The heading value as a Double within [0.0, 360.0). */ -fun Number?.toHeading(): Double = this?.toDouble()?.wrapIn(headingRange.start, headingRange.endInclusive) ?: DEFAULT_HEADING +fun Number?.toHeading(): Double = + this?.toDouble()?.wrapIn(headingRange.start, headingRange.endInclusive) ?: DEFAULT_HEADING /** * Converts a Number? to a valid tilt value (0.0 to 90.0). @@ -319,10 +320,7 @@ fun FlyAroundOptions.copy( } } -fun FlyToOptions.copy( - endCamera: Camera? = null, - durationInMillis: Long? = null, -): FlyToOptions { +fun FlyToOptions.copy(endCamera: Camera? = null, durationInMillis: Long? = null): FlyToOptions { val objectToCopy = this return flyToOptions { From f88bbfcfff59f85914e99527c818d89a1dbf50ab Mon Sep 17 00:00:00 2001 From: Dale Hawkins <107309+dkhawk@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:34:13 -0600 Subject: [PATCH 2/2] refactor(compass): extract tick and label drawing helpers to manage line length --- .../widgets/WhiskeyCompass.kt | 242 ++++++++++++------ 1 file changed, 158 insertions(+), 84 deletions(-) diff --git a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/WhiskeyCompass.kt b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/WhiskeyCompass.kt index aaf0e4fc..690d7f3a 100644 --- a/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/WhiskeyCompass.kt +++ b/maps3d-compose-demo/src/main/java/com/example/maps3dcomposedemo/widgets/WhiskeyCompass.kt @@ -20,8 +20,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextMeasurer import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.drawText import androidx.compose.ui.text.font.FontFamily @@ -124,94 +127,32 @@ fun WhiskeyCompass( ) { // Canvas 1: Scrolling Compass Strip (ticks, degree labels, cardinal labels) Canvas(modifier = Modifier.matchParentSize()) { - val majorTickHeightPx = majorTickHeight.toPx() - val minorTickHeightPx = minorTickHeight.toPx() - val majorTickStrokeWidthPx = majorTickStrokeWidth.toPx() - val minorTickStrokeWidthPx = minorTickStrokeWidth.toPx() - val degreeLabelVerticalOffsetPx = degreeLabelVerticalOffset.toPx() - val cardinalLabelVerticalOffsetPx = cardinalLabelVerticalOffset.toPx() - - val canvasWidth = size.width - val canvasCenterY = center.y + val params = CompassStripParams( + tickColor = tickColor, + majorTickHeightPx = majorTickHeight.toPx(), + minorTickHeightPx = minorTickHeight.toPx(), + majorTickStrokeWidthPx = majorTickStrokeWidth.toPx(), + minorTickStrokeWidthPx = minorTickStrokeWidth.toPx(), + showCardinalLabels = showCardinalLabels, + measuredCardinalLabels = measuredCardinalLabels, + cardinalLabelVerticalOffsetPx = cardinalLabelVerticalOffset.toPx(), + showDegreeLabels = showDegreeLabels, + degreeLabelInterval = degreeLabelInterval, + degreeLabelTextStyle = degreeLabelTextStyle, + degreeLabelVerticalOffsetPx = degreeLabelVerticalOffset.toPx(), + textMeasurer = textMeasurer, + ) val xOffset = center.x - (normalizedHeading * pixelsPerDegree) - val tickCenterY = canvasCenterY translate(left = xOffset) { - for (repetition in -1..1) { - val repetitionBaseDegree = repetition * 360 - for (degreeInRepetition in 0 until 360) { - val absoluteDegree = repetitionBaseDegree + degreeInRepetition - val xPos = absoluteDegree * pixelsPerDegree - - if (xPos < -xOffset + canvasWidth + canvasWidth && - xPos > -xOffset - canvasWidth - ) { - val isMajorTickEquivalent = degreeInRepetition % 10 == 0 - val isMinorTickEquivalent = - degreeInRepetition % 5 == 0 && !isMajorTickEquivalent - - if (isMajorTickEquivalent) { - val tickTopY = tickCenterY - majorTickHeightPx / 2f - val tickBottomY = tickCenterY + majorTickHeightPx / 2f - drawLine( - color = tickColor, - start = Offset(x = xPos, y = tickTopY), - end = Offset(x = xPos, y = tickBottomY), - strokeWidth = majorTickStrokeWidthPx, - ) - - if (showCardinalLabels && measuredCardinalLabels.containsKey( - degreeInRepetition, - ) - ) { - val measuredText = - measuredCardinalLabels.getValue(degreeInRepetition) - drawText( - textLayoutResult = measuredText, - topLeft = Offset( - x = xPos - measuredText.size.width / 2f, - y = - tickTopY - measuredText.size.height - - cardinalLabelVerticalOffsetPx, - ), - ) - } - } else if (isMinorTickEquivalent) { - val tickTopY = tickCenterY - minorTickHeightPx / 2f - val tickBottomY = tickCenterY + minorTickHeightPx / 2f - drawLine( - color = tickColor, - start = Offset(x = xPos, y = tickTopY), - end = Offset(x = xPos, y = tickBottomY), - strokeWidth = minorTickStrokeWidthPx, - ) - } - - if (showDegreeLabels && degreeInRepetition % degreeLabelInterval == 0) { - val tickBottomY = tickCenterY + ( - if (isMajorTickEquivalent) { - majorTickHeightPx - } else if (isMinorTickEquivalent) { - minorTickHeightPx - } else { - 0f - } - ) / 2f - val labelText = degreeInRepetition.toString() - val measuredText = - textMeasurer.measure(labelText, style = degreeLabelTextStyle) - drawText( - textLayoutResult = measuredText, - topLeft = Offset( - x = xPos - measuredText.size.width / 2f, - y = tickBottomY + degreeLabelVerticalOffsetPx, - ), - ) - } - } - } - } + drawCompassStrip( + params = params, + pixelsPerDegree = pixelsPerDegree, + xOffset = xOffset, + canvasWidth = size.width, + tickCenterY = center.y, + ) } } @@ -229,6 +170,139 @@ fun WhiskeyCompass( } } +private data class CompassStripParams( + val tickColor: Color, + val majorTickHeightPx: Float, + val minorTickHeightPx: Float, + val majorTickStrokeWidthPx: Float, + val minorTickStrokeWidthPx: Float, + val showCardinalLabels: Boolean, + val measuredCardinalLabels: Map, + val cardinalLabelVerticalOffsetPx: Float, + val showDegreeLabels: Boolean, + val degreeLabelInterval: Int, + val degreeLabelTextStyle: TextStyle, + val degreeLabelVerticalOffsetPx: Float, + val textMeasurer: TextMeasurer, +) + +private fun DrawScope.drawCompassStrip( + params: CompassStripParams, + pixelsPerDegree: Float, + xOffset: Float, + canvasWidth: Float, + tickCenterY: Float, +) { + for (repetition in -1..1) { + val repetitionBaseDegree = repetition * 360 + for (degreeInRepetition in 0 until 360) { + val absoluteDegree = repetitionBaseDegree + degreeInRepetition + val xPos = absoluteDegree * pixelsPerDegree + + if (xPos < -xOffset + 2 * canvasWidth && xPos > -xOffset - canvasWidth) { + drawDegreeTick( + degree = degreeInRepetition, + xPos = xPos, + tickCenterY = tickCenterY, + params = params, + ) + } + } + } +} + +private fun DrawScope.drawDegreeTick( + degree: Int, + xPos: Float, + tickCenterY: Float, + params: CompassStripParams, +) { + val isMajorTickEquivalent = degree % 10 == 0 + val isMinorTickEquivalent = degree % 5 == 0 && !isMajorTickEquivalent + + if (isMajorTickEquivalent) { + val tickTopY = tickCenterY - params.majorTickHeightPx / 2f + val tickBottomY = tickCenterY + params.majorTickHeightPx / 2f + drawLine( + color = params.tickColor, + start = Offset(x = xPos, y = tickTopY), + end = Offset(x = xPos, y = tickBottomY), + strokeWidth = params.majorTickStrokeWidthPx, + ) + + if (params.showCardinalLabels && params.measuredCardinalLabels.containsKey(degree)) { + val measuredText = params.measuredCardinalLabels.getValue(degree) + drawCardinalLabel( + xPos = xPos, + tickTopY = tickTopY, + measuredText = measuredText, + verticalOffsetPx = params.cardinalLabelVerticalOffsetPx, + ) + } + } else if (isMinorTickEquivalent) { + val tickTopY = tickCenterY - params.minorTickHeightPx / 2f + val tickBottomY = tickCenterY + params.minorTickHeightPx / 2f + drawLine( + color = params.tickColor, + start = Offset(x = xPos, y = tickTopY), + end = Offset(x = xPos, y = tickBottomY), + strokeWidth = params.minorTickStrokeWidthPx, + ) + } + + if (params.showDegreeLabels && degree % params.degreeLabelInterval == 0) { + val tickHeightPx = when { + isMajorTickEquivalent -> params.majorTickHeightPx + isMinorTickEquivalent -> params.minorTickHeightPx + else -> 0f + } + val tickBottomY = tickCenterY + tickHeightPx / 2f + val labelText = degree.toString() + val measuredText = params.textMeasurer.measure( + labelText, + style = params.degreeLabelTextStyle, + ) + drawDegreeLabel( + xPos = xPos, + tickBottomY = tickBottomY, + measuredText = measuredText, + verticalOffsetPx = params.degreeLabelVerticalOffsetPx, + ) + } +} + +private fun DrawScope.drawCardinalLabel( + xPos: Float, + tickTopY: Float, + measuredText: TextLayoutResult, + verticalOffsetPx: Float, +) { + val labelTopY = tickTopY - measuredText.size.height - verticalOffsetPx + drawText( + textLayoutResult = measuredText, + topLeft = Offset( + x = xPos - measuredText.size.width / 2f, + y = labelTopY, + ), + ) +} + +private fun DrawScope.drawDegreeLabel( + xPos: Float, + tickBottomY: Float, + measuredText: TextLayoutResult, + verticalOffsetPx: Float, +) { + val labelTopY = tickBottomY + verticalOffsetPx + drawText( + textLayoutResult = measuredText, + topLeft = Offset( + x = xPos - measuredText.size.width / 2f, + y = labelTopY, + ), + ) +} + @Preview(showBackground = true, backgroundColor = 0xFF222222) @Composable private fun FlatWhiskeyCompassPreview() {