From 693f608b14d6e7ed6415421d96c7a6074272ab4a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 13 Sep 2022 22:53:56 +0300 Subject: [PATCH] Reorder some API --- ...pFeatureBuilder.kt => MapFeaturesState.kt} | 85 ++++++++++--------- .../center/sciprog/maps/compose/MapView.kt | 50 +++++------ .../center/sciprog/maps/compose/drag.kt | 3 - .../center/sciprog/maps/compose/MapViewJvm.kt | 4 +- 4 files changed, 74 insertions(+), 68 deletions(-) rename maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/{MapFeatureBuilder.kt => MapFeaturesState.kt} (73%) delete mode 100644 maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/drag.kt diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeatureBuilder.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeaturesState.kt similarity index 73% rename from maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeatureBuilder.kt rename to maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeaturesState.kt index 234218b..a4b8de2 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeatureBuilder.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeaturesState.kt @@ -2,6 +2,7 @@ package center.sciprog.maps.compose import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.remember import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PointMode @@ -13,50 +14,57 @@ import center.sciprog.maps.coordinates.* public typealias FeatureId = String -public interface MapFeatureAttributeKey +public object DraggableAttribute : MapFeaturesState.Attribute - -public class MapFeatureAttributeSet(private val map: Map, *>) { - public operator fun get(key: MapFeatureAttributeKey<*>): T? = map[key]?.let { - @Suppress("UNCHECKED_CAST") - it as T - } -} - -public interface MapFeatureBuilder { - public fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId - - public fun setAttribute(id: FeatureId, key: MapFeatureAttributeKey, value: T) +public class MapFeaturesState internal constructor( + private val features: MutableMap, + private val attributes: MutableMap, in Any?>>, +) { + public interface Attribute //TODO use context receiver for that public fun FeatureId.draggable(enabled: Boolean = true) { setAttribute(this, DraggableAttribute, enabled) } -} -internal class MapFeatureBuilderImpl: MapFeatureBuilder { - - internal val features = mutableStateMapOf() - internal val attributes = SnapshotStateMap, in Any?>>() + public fun features(): Map = features private fun generateID(feature: MapFeature): FeatureId = "@feature[${feature.hashCode().toUInt()}]" - override fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId { + public fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId { val safeId = id ?: generateID(feature) features[id ?: generateID(feature)] = feature return safeId } - override fun setAttribute(id: FeatureId, key: MapFeatureAttributeKey, value: T) { - attributes.getOrPut(id) { SnapshotStateMap() }[key] = value + public fun setAttribute(id: FeatureId, key: MapFeaturesState.Attribute, value: T) { + attributes.getOrPut(id) { mutableStateMapOf() }[key] = value } + @Suppress("UNCHECKED_CAST") + public fun getAttribute(id: FeatureId, key: MapFeaturesState.Attribute): T? = + attributes[id]?.get(key)?.let { it as T } + @Suppress("UNCHECKED_CAST") + public fun findAllWithAttribute(key: MapFeaturesState.Attribute, condition: (T) -> Boolean): Set { + return attributes.filterValues { + condition(it[key] as T) + }.keys + } } +@Composable +public fun rememberMapFeatureState( + builder: @Composable MapFeaturesState.() -> Unit = {}, +): MapFeaturesState = remember(builder) { + MapFeaturesState( + mutableStateMapOf(), + mutableStateMapOf() + ) +}.apply { builder() } -public fun MapFeatureBuilder.circle( +public fun MapFeaturesState.circle( center: GeodeticMapCoordinates, zoomRange: IntRange = defaultZoomRange, size: Float = 5f, @@ -66,7 +74,7 @@ public fun MapFeatureBuilder.circle( id, MapCircleFeature(center, zoomRange, size, color) ) -public fun MapFeatureBuilder.circle( +public fun MapFeaturesState.circle( centerCoordinates: Pair, zoomRange: IntRange = defaultZoomRange, size: Float = 5f, @@ -76,7 +84,7 @@ public fun MapFeatureBuilder.circle( id, MapCircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color) ) -public fun MapFeatureBuilder.rectangle( +public fun MapFeaturesState.rectangle( centerCoordinates: Pair, zoomRange: IntRange = defaultZoomRange, size: DpSize = DpSize(5.dp, 5.dp), @@ -86,14 +94,14 @@ public fun MapFeatureBuilder.rectangle( id, MapRectangleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color) ) -public fun MapFeatureBuilder.draw( +public fun MapFeaturesState.draw( position: Pair, zoomRange: IntRange = defaultZoomRange, id: FeatureId? = null, drawFeature: DrawScope.() -> Unit, ): FeatureId = addFeature(id, MapDrawFeature(position.toCoordinates(), zoomRange, drawFeature)) -public fun MapFeatureBuilder.line( +public fun MapFeaturesState.line( aCoordinates: Gmc, bCoordinates: Gmc, zoomRange: IntRange = defaultZoomRange, @@ -104,7 +112,7 @@ public fun MapFeatureBuilder.line( MapLineFeature(aCoordinates, bCoordinates, zoomRange, color) ) -public fun MapFeatureBuilder.line( +public fun MapFeaturesState.line( curve: GmcCurve, zoomRange: IntRange = defaultZoomRange, color: Color = Color.Red, @@ -114,7 +122,7 @@ public fun MapFeatureBuilder.line( MapLineFeature(curve.forward.coordinates, curve.backward.coordinates, zoomRange, color) ) -public fun MapFeatureBuilder.line( +public fun MapFeaturesState.line( aCoordinates: Pair, bCoordinates: Pair, zoomRange: IntRange = defaultZoomRange, @@ -125,7 +133,7 @@ public fun MapFeatureBuilder.line( MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color) ) -public fun MapFeatureBuilder.arc( +public fun MapFeaturesState.arc( oval: GmcRectangle, startAngle: Angle, arcLength: Angle, @@ -137,7 +145,7 @@ public fun MapFeatureBuilder.arc( MapArcFeature(oval, startAngle, arcLength, zoomRange, color) ) -public fun MapFeatureBuilder.arc( +public fun MapFeaturesState.arc( center: Pair, radius: Distance, startAngle: Angle, @@ -156,7 +164,7 @@ public fun MapFeatureBuilder.arc( ) ) -public fun MapFeatureBuilder.points( +public fun MapFeaturesState.points( points: List, zoomRange: IntRange = defaultZoomRange, stroke: Float = 2f, @@ -166,7 +174,7 @@ public fun MapFeatureBuilder.points( ): FeatureId = addFeature(id, MapPointsFeature(points, zoomRange, stroke, color, pointMode)) @JvmName("pointsFromPairs") -public fun MapFeatureBuilder.points( +public fun MapFeaturesState.points( points: List>, zoomRange: IntRange = defaultZoomRange, stroke: Float = 2f, @@ -176,7 +184,7 @@ public fun MapFeatureBuilder.points( ): FeatureId = addFeature(id, MapPointsFeature(points.map { it.toCoordinates() }, zoomRange, stroke, color, pointMode)) @Composable -public fun MapFeatureBuilder.image( +public fun MapFeaturesState.image( position: Pair, image: ImageVector, size: DpSize = DpSize(20.dp, 20.dp), @@ -184,17 +192,18 @@ public fun MapFeatureBuilder.image( id: FeatureId? = null, ): FeatureId = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange)) -public fun MapFeatureBuilder.group( +@Composable +public fun MapFeaturesState.group( zoomRange: IntRange = defaultZoomRange, id: FeatureId? = null, - builder: MapFeatureBuilder.() -> Unit, + builder: @Composable MapFeaturesState.() -> Unit, ): FeatureId { - val map = MapFeatureBuilderImpl().apply(builder).features + val map = rememberMapFeatureState(builder).features() val feature = MapFeatureGroup(map, zoomRange) return addFeature(id, feature) } -public fun MapFeatureBuilder.text( +public fun MapFeaturesState.text( position: GeodeticMapCoordinates, text: String, zoomRange: IntRange = defaultZoomRange, @@ -203,7 +212,7 @@ public fun MapFeatureBuilder.text( id: FeatureId? = null, ): FeatureId = addFeature(id, MapTextFeature(position, text, zoomRange, color, font)) -public fun MapFeatureBuilder.text( +public fun MapFeaturesState.text( position: Pair, text: String, zoomRange: IntRange = defaultZoomRange, 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 cfccbe5..1a4bdbf 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 @@ -68,7 +68,7 @@ public data class MapViewConfig( public expect fun MapView( mapTileProvider: MapTileProvider, initialViewPoint: MapViewPoint, - features: Map, + features: MapFeaturesState, config: MapViewConfig = MapViewConfig(), modifier: Modifier = Modifier.fillMaxSize(), ) @@ -103,13 +103,12 @@ public fun MapView( initialRectangle: GmcRectangle? = null, config: MapViewConfig = MapViewConfig(), modifier: Modifier = Modifier.fillMaxSize(), - buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {}, + buildFeatures: @Composable (MapFeaturesState.() -> Unit) = {}, ): Unit = key(buildFeatures) { - val featuresBuilder = MapFeatureBuilderImpl().apply { buildFeatures() } + val featureState = rememberMapFeatureState(buildFeatures) - val features = featuresBuilder.features - val attributes = featuresBuilder.attributes + val features = featureState.features() var cachedCanvasSize: DpSize by remember { mutableStateOf(defaultCanvasSize) } @@ -120,36 +119,37 @@ public fun MapView( ?: MapViewPoint.globe } - val featureDrag = DragHandle.withPrimaryButton { _, start, end -> - val zoom = start.zoom - attributes.filterValues { - it[DraggableAttribute] as? Boolean ?: false - }.keys.forEach { id -> - val feature = features[id] as? DraggableMapFeature ?: return@forEach - //val border = WebMercatorProjection.scaleFactor(zoom) - val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach - if (start.focus in boundingBox) { - features[id] = feature.withCoordinates(end.focus) - return@withPrimaryButton false + val featureDrag by 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 } - return@withPrimaryButton true } - val newConfig = config.copy( - dragHandle = DragHandle.combine(featureDrag, config.dragHandle), - onCanvasSizeChange = { canvasSize -> - config.onCanvasSizeChange(canvasSize) - cachedCanvasSize = canvasSize - } - ) + val newConfig by derivedStateOf { + config.copy( + dragHandle = DragHandle.combine(featureDrag, config.dragHandle), + onCanvasSizeChange = { canvasSize -> + config.onCanvasSizeChange(canvasSize) + cachedCanvasSize = canvasSize + } + ) + } MapView( mapTileProvider = mapTileProvider, initialViewPoint = viewPointOverride, - features = features, + features = featureState, config = newConfig, modifier = modifier, ) diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/drag.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/drag.kt deleted file mode 100644 index 81e117d..0000000 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/drag.kt +++ /dev/null @@ -1,3 +0,0 @@ -package center.sciprog.maps.compose - -public object DraggableAttribute: MapFeatureAttributeKey \ No newline at end of file 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 f22e8b2..ef581cd 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 @@ -50,7 +50,7 @@ private val logger = KotlinLogging.logger("MapView") public actual fun MapView( mapTileProvider: MapTileProvider, initialViewPoint: MapViewPoint, - features: Map, + features: MapFeaturesState, config: MapViewConfig, modifier: Modifier, ): Unit = key(initialViewPoint) { @@ -334,7 +334,7 @@ public actual fun MapView( dstSize = tileSize ) } - features.values.filter { zoom in it.zoomRange }.forEach { feature -> + features.features().values.filter { zoom in it.zoomRange }.forEach { feature -> drawFeature(zoom, feature) } }