Skip to content

Commit

Permalink
Reimplement Scalebar (#232)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Sargun Vohra <[email protected]>
  • Loading branch information
3 people authored Jan 8, 2025
1 parent 8b7b868 commit ef3ff80
Show file tree
Hide file tree
Showing 14 changed files with 570 additions and 311 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ fun DemoMapControls(
) {
if (Platform.supportsBlending) {
Box(modifier = modifier.fillMaxSize().padding(8.dp)) {
DisappearingScaleBar(cameraState, modifier = Modifier.align(Alignment.TopStart))
DisappearingScaleBar(
metersPerDp = cameraState.metersPerDpAtTarget,
zoom = cameraState.position.zoom,
modifier = Modifier.align(Alignment.TopStart),
)
DisappearingCompassButton(
cameraState,
modifier = Modifier.align(Alignment.TopEnd),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fun Material3() {
)

Box(modifier = Modifier.fillMaxSize().padding(8.dp)) {
ScaleBar(cameraState, modifier = Modifier.align(Alignment.TopStart))
ScaleBar(cameraState.metersPerDpAtTarget, modifier = Modifier.align(Alignment.TopStart))
CompassButton(cameraState, modifier = Modifier.align(Alignment.TopEnd))
AttributionButton(styleState, modifier = Modifier.align(Alignment.BottomEnd))
}
Expand All @@ -49,7 +49,11 @@ fun Material3() {
)

Box(modifier = Modifier.fillMaxSize().padding(8.dp)) {
DisappearingScaleBar(cameraState, modifier = Modifier.align(Alignment.TopStart)) // (1)!
DisappearingScaleBar(
metersPerDp = cameraState.metersPerDpAtTarget,
zoom = cameraState.position.zoom,
modifier = Modifier.align(Alignment.TopStart),
) // (1)!
DisappearingCompassButton(cameraState, modifier = Modifier.align(Alignment.TopEnd)) // (2)!
AttributionButton(styleState, modifier = Modifier.align(Alignment.BottomEnd))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.sargunv.maplibrecompose.material3

import android.icu.util.LocaleData
import android.icu.util.ULocale
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure

@Composable
internal actual fun systemDefaultPrimaryMeasure(): ScaleBarMeasure? {
if (android.os.Build.VERSION.SDK_INT < 28) return null
val locales = LocalContext.current.resources.configuration.locales
if (locales.isEmpty) return null
return when (LocaleData.getMeasurementSystem(ULocale.forLocale(locales[0]))) {
LocaleData.MeasurementSystem.SI -> ScaleBarMeasure.Metric
LocaleData.MeasurementSystem.US -> ScaleBarMeasure.FeetAndMiles
LocaleData.MeasurementSystem.UK -> ScaleBarMeasure.YardsAndMiles
else -> null
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
<string name="meters_symbol">m</string>
<string name="kilometers_symbol">km</string>
<string name="feet_symbol">ft</string>
<string name="yards_symbol">yd</string>
<string name="miles_symbol">mi</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package dev.sargunv.maplibrecompose.material3

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope.Companion.DefaultBlendMode
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.drawText

/** Draw several lines. Each offset in [path] is relative to the previous. */
internal fun DrawScope.drawPath(
color: Color,
path: List<Offset>,
strokeWidth: Float = Stroke.HairlineWidth,
cap: StrokeCap = Stroke.DefaultCap,
pathEffect: PathEffect? = null,
alpha: Float = 1.0f,
colorFilter: ColorFilter? = null,
blendMode: BlendMode = DefaultBlendMode,
) {
val it = path.iterator()
if (!it.hasNext()) return
var start = it.next()
while (it.hasNext()) {
val end = start + it.next()
drawLine(
color = color,
start = start,
end = end,
strokeWidth = strokeWidth,
cap = cap,
pathEffect = pathEffect,
alpha = alpha,
colorFilter = colorFilter,
blendMode = blendMode,
)
start = end
}
}

/** Draw several paths with halo. All halos of all [paths] are behind all strokes. */
internal fun DrawScope.drawPathsWithHalo(
color: Color,
haloColor: Color,
paths: List<List<Offset>>,
strokeWidth: Float = Stroke.HairlineWidth,
haloWidth: Float = Stroke.HairlineWidth,
cap: StrokeCap = Stroke.DefaultCap,
) {
for (path in paths) {
drawPath(color = haloColor, path = path, strokeWidth = strokeWidth + haloWidth * 2, cap = cap)
}
for (path in paths) {
drawPath(color = color, path = path, strokeWidth = strokeWidth, cap = cap)
}
}

internal fun DrawScope.drawTextWithHalo(
textLayoutResult: TextLayoutResult,
topLeft: Offset = Offset.Zero,
color: Color = Color.Unspecified,
haloColor: Color = Color.Unspecified,
haloWidth: Float = 0f,
) {
// * 2 because the stroke is painted half outside and half inside of the text shape
val stroke = Stroke(width = haloWidth * 2, cap = StrokeCap.Round, join = StrokeJoin.Round)
drawText(
textLayoutResult = textLayoutResult,
color = haloColor,
topLeft = topLeft,
drawStyle = stroke,
)
drawText(textLayoutResult = textLayoutResult, color = color, topLeft = topLeft, drawStyle = Fill)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package dev.sargunv.maplibrecompose.material3.controls

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import dev.sargunv.maplibrecompose.material3.defaultScaleBarMeasures
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.delay

/**
* An animated scale bar that appears when the [zoom] level of the map changes, and then disappears
* after [visibilityDuration]. This composable wraps [ScaleBar] with visibility animations.
*
* @param metersPerDp how many meters are displayed in one device independent pixel (dp), i.e. the
* scale. See
* [CameraState.metersPerDpAtTarget][dev.sargunv.maplibrecompose.compose.CameraState.metersPerDpAtTarget]
* @param zoom zoom level of the map
* @param modifier the [Modifier] to be applied to this layout node
* @param measures which measures to show on the scale bar. If `null`, measures will be selected
* based on the system settings or otherwise the user's locale.
* @param haloColor halo for better visibility when displayed on top of the map
* @param color scale bar and text color.
* @param textStyle the text style. The text size is the deciding factor how large the scale bar is
* is displayed.
* @param alignment horizontal alignment of the scale bar and text
* @param visibilityDuration how long it should be visible after the zoom changed
* @param enterTransition EnterTransition(s) used for the appearing animation
* @param exitTransition ExitTransition(s) used for the disappearing animation
*/
@Composable
public fun DisappearingScaleBar(
metersPerDp: Double,
zoom: Double,
modifier: Modifier = Modifier,
measures: ScaleBarMeasures = defaultScaleBarMeasures(),
haloColor: Color = MaterialTheme.colorScheme.surface,
color: Color = contentColorFor(haloColor),
textStyle: TextStyle = MaterialTheme.typography.labelMedium,
alignment: Alignment.Horizontal = Alignment.Start,
visibilityDuration: Duration = 3.seconds,
enterTransition: EnterTransition = fadeIn(),
exitTransition: ExitTransition = fadeOut(),
) {
val visible = remember { MutableTransitionState(true) }

LaunchedEffect(zoom) {
// Show ScaleBar
visible.targetState = true
delay(visibilityDuration)
// Hide ScaleBar after timeout period
visible.targetState = false
}

AnimatedVisibility(
visibleState = visible,
modifier = modifier,
enter = enterTransition,
exit = exitTransition,
) {
ScaleBar(
metersPerDp = metersPerDp,
measures = measures,
haloColor = haloColor,
color = color,
textStyle = textStyle,
alignment = alignment,
)
}
}
Loading

0 comments on commit ef3ff80

Please sign in to comment.