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