diff --git a/demo/maps/src/jvmMain/kotlin/Main.kt b/demo/maps/src/jvmMain/kotlin/Main.kt index 1f19eeb..89a820e 100644 --- a/demo/maps/src/jvmMain/kotlin/Main.kt +++ b/demo/maps/src/jvmMain/kotlin/Main.kt @@ -111,7 +111,7 @@ fun App() { //remember feature ID circle( centerCoordinates = pointTwo, - ).updates(scope) { + ).updated(scope) { delay(200) //Overwrite a feature with new color it.copy(color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())) diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeature.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeature.kt index b78d7d9..f3aeca1 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeature.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeature.kt @@ -16,7 +16,12 @@ import center.sciprog.maps.coordinates.* import kotlin.math.floor public interface MapFeature { + public interface Attribute + public val zoomRange: IntRange + + public val attributes: AttributeMap + public fun getBoundingBox(zoom: Double): GmcRectangle? } @@ -33,7 +38,7 @@ public interface DraggableMapFeature : SelectableMapFeature { public fun Iterable.computeBoundingBox(zoom: Double): GmcRectangle? = mapNotNull { it.getBoundingBox(zoom) }.wrapAll() -public fun Pair.toCoordinates() = +public fun Pair.toCoordinates(): GeodeticMapCoordinates = GeodeticMapCoordinates.ofDegrees(first.toDouble(), second.toDouble()) internal val defaultZoomRange = 1..18 @@ -42,6 +47,7 @@ internal val defaultZoomRange = 1..18 * A feature that decides what to show depending on the zoom value (it could change size of shape) */ public class MapFeatureSelector( + override val attributes: AttributeMap = AttributeMap(), public val selector: (zoom: Int) -> MapFeature, ) : MapFeature { override val zoomRange: IntRange get() = defaultZoomRange @@ -52,6 +58,7 @@ public class MapFeatureSelector( public class MapDrawFeature( public val position: GeodeticMapCoordinates, override val zoomRange: IntRange = defaultZoomRange, + override val attributes: AttributeMap = AttributeMap(), public val drawFeature: DrawScope.() -> Unit, ) : DraggableMapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle { @@ -60,7 +67,7 @@ public class MapDrawFeature( } override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature = - MapDrawFeature(newCoordinates, zoomRange, drawFeature) + MapDrawFeature(newCoordinates, zoomRange, attributes, drawFeature) } public class MapPathFeature( @@ -70,6 +77,7 @@ public class MapPathFeature( public val style: DrawStyle = Fill, public val targetRect: Rect = path.getBounds(), override val zoomRange: IntRange = defaultZoomRange, + override val attributes: AttributeMap = AttributeMap(), ) : DraggableMapFeature { override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature = MapPathFeature(rectangle.moveTo(newCoordinates), path, brush, style, targetRect, zoomRange) @@ -84,6 +92,7 @@ public class MapPointsFeature( public val stroke: Float = 2f, public val color: Color = Color.Red, public val pointMode: PointMode = PointMode.Points, + override val attributes: AttributeMap = AttributeMap(), ) : MapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(points.first(), points.last()) } @@ -93,6 +102,7 @@ public data class MapCircleFeature( override val zoomRange: IntRange = defaultZoomRange, public val size: Float = 5f, public val color: Color = Color.Red, + override val attributes: AttributeMap = AttributeMap(), ) : DraggableMapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle { val scale = WebMercatorProjection.scaleFactor(zoom) @@ -100,7 +110,7 @@ public data class MapCircleFeature( } override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature = - MapCircleFeature(newCoordinates, zoomRange, size, color) + MapCircleFeature(newCoordinates, zoomRange, size, color, attributes) } public class MapRectangleFeature( @@ -108,6 +118,7 @@ public class MapRectangleFeature( override val zoomRange: IntRange = defaultZoomRange, public val size: DpSize = DpSize(5.dp, 5.dp), public val color: Color = Color.Red, + override val attributes: AttributeMap = AttributeMap(), ) : DraggableMapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle { val scale = WebMercatorProjection.scaleFactor(zoom) @@ -115,7 +126,7 @@ public class MapRectangleFeature( } override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature = - MapRectangleFeature(newCoordinates, zoomRange, size, color) + MapRectangleFeature(newCoordinates, zoomRange, size, color, attributes) } public class MapLineFeature( @@ -123,6 +134,7 @@ public class MapLineFeature( public val b: GeodeticMapCoordinates, override val zoomRange: IntRange = defaultZoomRange, public val color: Color = Color.Red, + override val attributes: AttributeMap = AttributeMap(), ) : SelectableMapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(a, b) @@ -141,11 +153,12 @@ public class MapArcFeature( public val arcLength: Angle, override val zoomRange: IntRange = defaultZoomRange, public val color: Color = Color.Red, + override val attributes: AttributeMap = AttributeMap(), ) : DraggableMapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle = oval override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature = - MapArcFeature(oval.moveTo(newCoordinates), startAngle, arcLength, zoomRange, color) + MapArcFeature(oval.moveTo(newCoordinates), startAngle, arcLength, zoomRange, color, attributes) } public class MapBitmapImageFeature( @@ -153,11 +166,12 @@ public class MapBitmapImageFeature( public val image: ImageBitmap, public val size: IntSize = IntSize(15, 15), override val zoomRange: IntRange = defaultZoomRange, + override val attributes: AttributeMap = AttributeMap(), ) : DraggableMapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position) override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature = - MapBitmapImageFeature(newCoordinates, image, size, zoomRange) + MapBitmapImageFeature(newCoordinates, image, size, zoomRange, attributes) } public class MapVectorImageFeature( @@ -165,11 +179,12 @@ public class MapVectorImageFeature( public val image: ImageVector, public val size: DpSize = DpSize(20.dp, 20.dp), override val zoomRange: IntRange = defaultZoomRange, + override val attributes: AttributeMap = AttributeMap(), ) : DraggableMapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position) override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature = - MapVectorImageFeature(newCoordinates, image, size, zoomRange) + MapVectorImageFeature(newCoordinates, image, size, zoomRange, attributes) @Composable public fun painter(): VectorPainter = rememberVectorPainter(image) @@ -181,6 +196,7 @@ public class MapVectorImageFeature( public class MapFeatureGroup( public val children: Map, MapFeature>, override val zoomRange: IntRange = defaultZoomRange, + override val attributes: AttributeMap = AttributeMap(), ) : MapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll() @@ -190,11 +206,12 @@ public class MapTextFeature( public val position: GeodeticMapCoordinates, public val text: String, override val zoomRange: IntRange = defaultZoomRange, - public val color: Color, + public val color: Color = Color.Black, + override val attributes: AttributeMap = AttributeMap(), public val fontConfig: MapTextFeatureFont.() -> Unit, ) : DraggableMapFeature { override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position) override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature = - MapTextFeature(newCoordinates, text, zoomRange, color, fontConfig) + MapTextFeature(newCoordinates, text, zoomRange, color, attributes, fontConfig) } 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 fc3dcc4..c4ad1ff 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 @@ -3,7 +3,6 @@ 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 import androidx.compose.ui.graphics.drawscope.DrawScope @@ -19,15 +18,10 @@ import kotlinx.coroutines.launch @JvmInline public value class FeatureId(public val id: String) -public object DraggableAttribute : MapFeaturesState.Attribute -public object SelectableAttribute : MapFeaturesState.Attribute<(FeatureId<*>, SelectableMapFeature) -> Unit> - public class MapFeaturesState internal constructor( private val featureMap: MutableMap, - @PublishedApi internal val attributeMap: MutableMap, in Any?>>, + //@PublishedApi internal val attributeMap: MutableMap, in Any?>>, ) { - public interface Attribute - //TODO use context receiver for that public fun FeatureId.draggable( //TODO add constraints @@ -50,12 +44,12 @@ public class MapFeaturesState internal constructor( /** * Cyclic update of a feature. Called infinitely until canceled. */ - public fun FeatureId.updates( + public fun FeatureId.updated( scope: CoroutineScope, update: suspend (T) -> T, ): Job = scope.launch { while (isActive) { - feature(this@updates, update(getFeature(this@updates))) + feature(this@updated, update(getFeature(this@updated))) } } @@ -83,16 +77,17 @@ public class MapFeaturesState internal constructor( public fun feature(id: FeatureId?, feature: T): FeatureId = feature(id?.id, feature) - public fun setAttribute(id: FeatureId<*>, key: Attribute, value: T) { + public fun setAttribute(id: FeatureId<*>, key: MapFeature.Attribute, value: T) { + feature(id,getFeature(id).at) attributeMap.getOrPut(id.id) { mutableStateMapOf() }[key] = value } - public fun removeAttribute(id: FeatureId<*>, key: Attribute<*>) { + public fun removeAttribute(id: FeatureId<*>, key: MapFeature.Attribute<*>) { attributeMap[id.id]?.remove(key) } @Suppress("UNCHECKED_CAST") - public fun getAttribute(id: FeatureId<*>, key: Attribute): T? = + public fun getAttribute(id: FeatureId<*>, key: MapFeature.Attribute): T? = attributeMap[id.id]?.get(key)?.let { it as T } // @Suppress("UNCHECKED_CAST") @@ -103,7 +98,7 @@ public class MapFeaturesState internal constructor( // } public inline fun forEachWithAttribute( - key: Attribute, + key: MapFeature.Attribute, block: (id: FeatureId<*>, attributeValue: T) -> Unit, ) { attributeMap.forEach { (id, attributeMap) -> diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/attributes.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/attributes.kt new file mode 100644 index 0000000..4fcb269 --- /dev/null +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/attributes.kt @@ -0,0 +1,23 @@ +package center.sciprog.maps.compose + +import androidx.compose.ui.graphics.Color + +public object DraggableAttribute : MapFeature.Attribute +public object SelectableAttribute : MapFeature.Attribute<(FeatureId<*>, SelectableMapFeature) -> Unit> +public object VisibleAttribute : MapFeature.Attribute + +public object ColorAttribute: MapFeature.Attribute + +@JvmInline +public value class AttributeMap internal constructor(private val map: Map, *>) { + + public fun > withAttribute( + attribute: A, + value: T, + ): AttributeMap = AttributeMap(map + (attribute to value)) + + @Suppress("UNCHECKED_CAST") + public operator fun get(attribute: MapFeature.Attribute): T? = map[attribute] as? T +} + +public fun AttributeMap(): AttributeMap = AttributeMap(emptyMap, Any?>()) \ No newline at end of file