diff --git a/build.gradle.kts b/build.gradle.kts index 994ab4b..d6c6c0b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ val ktorVersion by extra("2.0.3") allprojects { group = "center.sciprog" - version = "0.1.0-dev-5" + version = "0.1.0-dev-6" } ksciencePublish{ diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeaturesState.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeaturesState.kt index 38a26c9..e118d88 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeaturesState.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeaturesState.kt @@ -52,16 +52,29 @@ public class MapFeaturesState internal constructor( condition(it[key] as T) }.keys } -} + public companion object{ -@Composable -public fun rememberMapFeatureState( - builder: MapFeaturesState.() -> Unit = {}, -): MapFeaturesState = remember(builder) { - MapFeaturesState( - mutableStateMapOf(), - mutableStateMapOf() - ).apply(builder) + /** + * Build, but do not remember map feature state + */ + public fun build( + builder: MapFeaturesState.() -> Unit = {}, + ): MapFeaturesState = MapFeaturesState( + mutableStateMapOf(), + mutableStateMapOf() + ).apply(builder) + + /** + * Build and remember map feature state + */ + @Composable + public fun remember( + builder: MapFeaturesState.() -> Unit = {}, + ): MapFeaturesState = remember(builder) { + build(builder) + } + + } } public fun MapFeaturesState.circle( diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt index 1f579ba..825cbc3 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt @@ -1,7 +1,9 @@ package center.sciprog.maps.compose import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.key +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.isPrimaryPressed @@ -68,15 +70,16 @@ public data class MapViewConfig( public expect fun MapView( mapTileProvider: MapTileProvider, initialViewPoint: MapViewPoint, - features: MapFeaturesState, + featuresState: MapFeaturesState, config: MapViewConfig = MapViewConfig(), modifier: Modifier = Modifier.fillMaxSize(), ) +internal val defaultCanvasSize = DpSize(512.dp, 512.dp) -internal fun GmcRectangle.computeViewPoint( +public fun GmcRectangle.computeViewPoint( mapTileProvider: MapTileProvider, - canvasSize: DpSize, + canvasSize: DpSize = defaultCanvasSize, ): MapViewPoint { val zoom = log2( min( @@ -87,7 +90,33 @@ internal fun GmcRectangle.computeViewPoint( return MapViewPoint(center, zoom) } -internal val defaultCanvasSize = DpSize(512.dp, 512.dp) +/** + * A builder for a Map with static features. + */ +@Composable +public fun MapView( + mapTileProvider: MapTileProvider, + initialViewPoint: MapViewPoint? = null, + initialRectangle: GmcRectangle? = null, + featureMap: Map, + config: MapViewConfig = MapViewConfig(), + modifier: Modifier = Modifier.fillMaxSize(), +) { + val featuresState = key(featureMap) { + MapFeaturesState.build { + featureMap.forEach(::addFeature) + } + } + + val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) { + initialViewPoint + ?: initialRectangle?.computeViewPoint(mapTileProvider) + ?: featureMap.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider) + ?: MapViewPoint.globe + } + + MapView(mapTileProvider, viewPointOverride, featuresState, config, modifier) +} /** * Draw a map using convenient parameters. If neither [initialViewPoint], noe [initialRectangle] is defined, @@ -104,50 +133,40 @@ public fun MapView( config: MapViewConfig = MapViewConfig(), modifier: Modifier = Modifier.fillMaxSize(), buildFeatures: MapFeaturesState.() -> Unit = {}, -): Unit = key(buildFeatures) { - - val featureState = rememberMapFeatureState(buildFeatures) +) { + val featureState = MapFeaturesState.remember(buildFeatures) val features = featureState.features() val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) { initialViewPoint - ?: initialRectangle?.computeViewPoint(mapTileProvider, defaultCanvasSize) - ?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider, defaultCanvasSize) + ?: initialRectangle?.computeViewPoint(mapTileProvider) + ?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider) ?: MapViewPoint.globe } - val featureDrag by remember { - derivedStateOf { - DragHandle.withPrimaryButton { _, start, end -> - val zoom = start.zoom - featureState.findAllWithAttribute(DraggableAttribute) { it }.forEach { id -> - val feature = features[id] as? DraggableMapFeature ?: return@forEach - val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach - if (start.focus in boundingBox) { - featureState.addFeature(id, feature.withCoordinates(end.focus)) - return@withPrimaryButton false - } - } - return@withPrimaryButton true + val featureDrag = DragHandle.withPrimaryButton { _, start, end -> + val zoom = start.zoom + featureState.findAllWithAttribute(DraggableAttribute) { it }.forEach { id -> + val feature = features[id] as? DraggableMapFeature ?: return@forEach + val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach + if (start.focus in boundingBox) { + featureState.addFeature(id, feature.withCoordinates(end.focus)) + return@withPrimaryButton false } } + return@withPrimaryButton true } - val newConfig by remember { - derivedStateOf { - config.copy( - dragHandle = DragHandle.combine(featureDrag, config.dragHandle) - ) - } - } - + val newConfig = config.copy( + dragHandle = DragHandle.combine(featureDrag, config.dragHandle) + ) MapView( mapTileProvider = mapTileProvider, initialViewPoint = viewPointOverride, - features = featureState, + featuresState = featureState, config = newConfig, modifier = modifier, ) diff --git a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt index 3c4120a..d7fa804 100644 --- a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt +++ b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt @@ -49,7 +49,7 @@ private val logger = KotlinLogging.logger("MapView") public actual fun MapView( mapTileProvider: MapTileProvider, initialViewPoint: MapViewPoint, - features: MapFeaturesState, + featuresState: MapFeaturesState, config: MapViewConfig, modifier: Modifier, ): Unit = key(initialViewPoint) { @@ -216,8 +216,8 @@ public actual fun MapView( } } - val painterCache = features.features().values.filterIsInstance().associateWith { it.painter() } - + val painterCache = featuresState.features().values.filterIsInstance() + .associateWith { it.painter() } Canvas(canvasModifier) { fun WebMercatorCoordinates.toOffset(): Offset = Offset( @@ -338,10 +338,12 @@ public actual fun MapView( dstSize = tileSize ) } - features.features().values.filter { zoom in it.zoomRange }.forEach { feature -> + + featuresState.features().values.filter { zoom in it.zoomRange }.forEach { feature -> drawFeature(zoom, feature) } } + selectRect?.let { rect -> drawRect( color = Color.Blue,