Reorder some API

This commit is contained in:
Alexander Nozik 2022-09-13 22:53:56 +03:00
parent 4c71b8e7bb
commit 693f608b14
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
4 changed files with 74 additions and 68 deletions

View File

@ -2,6 +2,7 @@ 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.snapshots.SnapshotStateMap 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
@ -13,50 +14,57 @@ import center.sciprog.maps.coordinates.*
public typealias FeatureId = String public typealias FeatureId = String
public interface MapFeatureAttributeKey<T> public object DraggableAttribute : MapFeaturesState.Attribute<Boolean>
public class MapFeaturesState internal constructor(
public class MapFeatureAttributeSet(private val map: Map<MapFeatureAttributeKey<*>, *>) { private val features: MutableMap<FeatureId, MapFeature>,
public operator fun <T> get(key: MapFeatureAttributeKey<*>): T? = map[key]?.let { private val attributes: MutableMap<FeatureId, SnapshotStateMap<Attribute<out Any?>, in Any?>>,
@Suppress("UNCHECKED_CAST") ) {
it as T public interface Attribute<T>
}
}
public interface MapFeatureBuilder {
public fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId
public fun <T> setAttribute(id: FeatureId, key: MapFeatureAttributeKey<T>, value: T)
//TODO use context receiver for that //TODO use context receiver for that
public fun FeatureId.draggable(enabled: Boolean = true) { public fun FeatureId.draggable(enabled: Boolean = true) {
setAttribute(this, DraggableAttribute, enabled) setAttribute(this, DraggableAttribute, enabled)
} }
}
internal class MapFeatureBuilderImpl: MapFeatureBuilder { public fun features(): Map<FeatureId, MapFeature> = features
internal val features = mutableStateMapOf<FeatureId, MapFeature>()
internal val attributes = SnapshotStateMap<FeatureId, SnapshotStateMap<MapFeatureAttributeKey<out Any?>, in Any?>>()
private fun generateID(feature: MapFeature): FeatureId = "@feature[${feature.hashCode().toUInt()}]" 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) val safeId = id ?: generateID(feature)
features[id ?: generateID(feature)] = feature features[id ?: generateID(feature)] = feature
return safeId return safeId
} }
override fun <T> setAttribute(id: FeatureId, key: MapFeatureAttributeKey<T>, value: T) { public fun <T> setAttribute(id: FeatureId, key: MapFeaturesState.Attribute<T>, value: T) {
attributes.getOrPut(id) { SnapshotStateMap() }[key] = value attributes.getOrPut(id) { mutableStateMapOf() }[key] = value
} }
@Suppress("UNCHECKED_CAST")
public fun <T> getAttribute(id: FeatureId, key: MapFeaturesState.Attribute<T>): T? =
attributes[id]?.get(key)?.let { it as T }
@Suppress("UNCHECKED_CAST")
public fun <T> findAllWithAttribute(key: MapFeaturesState.Attribute<T>, condition: (T) -> Boolean): Set<FeatureId> {
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, center: GeodeticMapCoordinates,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
size: Float = 5f, size: Float = 5f,
@ -66,7 +74,7 @@ public fun MapFeatureBuilder.circle(
id, MapCircleFeature(center, zoomRange, size, color) id, MapCircleFeature(center, zoomRange, size, color)
) )
public fun MapFeatureBuilder.circle( public fun MapFeaturesState.circle(
centerCoordinates: Pair<Double, Double>, centerCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
size: Float = 5f, size: Float = 5f,
@ -76,7 +84,7 @@ public fun MapFeatureBuilder.circle(
id, MapCircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color) id, MapCircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
) )
public fun MapFeatureBuilder.rectangle( public fun MapFeaturesState.rectangle(
centerCoordinates: Pair<Double, Double>, centerCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
size: DpSize = DpSize(5.dp, 5.dp), size: DpSize = DpSize(5.dp, 5.dp),
@ -86,14 +94,14 @@ public fun MapFeatureBuilder.rectangle(
id, MapRectangleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color) id, MapRectangleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
) )
public fun MapFeatureBuilder.draw( public fun MapFeaturesState.draw(
position: Pair<Double, Double>, position: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null, id: FeatureId? = null,
drawFeature: DrawScope.() -> Unit, drawFeature: DrawScope.() -> Unit,
): FeatureId = addFeature(id, MapDrawFeature(position.toCoordinates(), zoomRange, drawFeature)) ): FeatureId = addFeature(id, MapDrawFeature(position.toCoordinates(), zoomRange, drawFeature))
public fun MapFeatureBuilder.line( public fun MapFeaturesState.line(
aCoordinates: Gmc, aCoordinates: Gmc,
bCoordinates: Gmc, bCoordinates: Gmc,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
@ -104,7 +112,7 @@ public fun MapFeatureBuilder.line(
MapLineFeature(aCoordinates, bCoordinates, zoomRange, color) MapLineFeature(aCoordinates, bCoordinates, zoomRange, color)
) )
public fun MapFeatureBuilder.line( public fun MapFeaturesState.line(
curve: GmcCurve, curve: GmcCurve,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red, color: Color = Color.Red,
@ -114,7 +122,7 @@ public fun MapFeatureBuilder.line(
MapLineFeature(curve.forward.coordinates, curve.backward.coordinates, zoomRange, color) MapLineFeature(curve.forward.coordinates, curve.backward.coordinates, zoomRange, color)
) )
public fun MapFeatureBuilder.line( public fun MapFeaturesState.line(
aCoordinates: Pair<Double, Double>, aCoordinates: Pair<Double, Double>,
bCoordinates: Pair<Double, Double>, bCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
@ -125,7 +133,7 @@ public fun MapFeatureBuilder.line(
MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color) MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color)
) )
public fun MapFeatureBuilder.arc( public fun MapFeaturesState.arc(
oval: GmcRectangle, oval: GmcRectangle,
startAngle: Angle, startAngle: Angle,
arcLength: Angle, arcLength: Angle,
@ -137,7 +145,7 @@ public fun MapFeatureBuilder.arc(
MapArcFeature(oval, startAngle, arcLength, zoomRange, color) MapArcFeature(oval, startAngle, arcLength, zoomRange, color)
) )
public fun MapFeatureBuilder.arc( public fun MapFeaturesState.arc(
center: Pair<Double, Double>, center: Pair<Double, Double>,
radius: Distance, radius: Distance,
startAngle: Angle, startAngle: Angle,
@ -156,7 +164,7 @@ public fun MapFeatureBuilder.arc(
) )
) )
public fun MapFeatureBuilder.points( public fun MapFeaturesState.points(
points: List<Gmc>, points: List<Gmc>,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
stroke: Float = 2f, stroke: Float = 2f,
@ -166,7 +174,7 @@ public fun MapFeatureBuilder.points(
): FeatureId = addFeature(id, MapPointsFeature(points, zoomRange, stroke, color, pointMode)) ): FeatureId = addFeature(id, MapPointsFeature(points, zoomRange, stroke, color, pointMode))
@JvmName("pointsFromPairs") @JvmName("pointsFromPairs")
public fun MapFeatureBuilder.points( public fun MapFeaturesState.points(
points: List<Pair<Double, Double>>, points: List<Pair<Double, Double>>,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
stroke: Float = 2f, stroke: Float = 2f,
@ -176,7 +184,7 @@ public fun MapFeatureBuilder.points(
): FeatureId = addFeature(id, MapPointsFeature(points.map { it.toCoordinates() }, zoomRange, stroke, color, pointMode)) ): FeatureId = addFeature(id, MapPointsFeature(points.map { it.toCoordinates() }, zoomRange, stroke, color, pointMode))
@Composable @Composable
public fun MapFeatureBuilder.image( public fun MapFeaturesState.image(
position: Pair<Double, Double>, position: Pair<Double, Double>,
image: ImageVector, image: ImageVector,
size: DpSize = DpSize(20.dp, 20.dp), size: DpSize = DpSize(20.dp, 20.dp),
@ -184,17 +192,18 @@ public fun MapFeatureBuilder.image(
id: FeatureId? = null, id: FeatureId? = null,
): FeatureId = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange)) ): FeatureId = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange))
public fun MapFeatureBuilder.group( @Composable
public fun MapFeaturesState.group(
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null, id: FeatureId? = null,
builder: MapFeatureBuilder.() -> Unit, builder: @Composable MapFeaturesState.() -> Unit,
): FeatureId { ): FeatureId {
val map = MapFeatureBuilderImpl().apply(builder).features val map = rememberMapFeatureState(builder).features()
val feature = MapFeatureGroup(map, zoomRange) val feature = MapFeatureGroup(map, zoomRange)
return addFeature(id, feature) return addFeature(id, feature)
} }
public fun MapFeatureBuilder.text( public fun MapFeaturesState.text(
position: GeodeticMapCoordinates, position: GeodeticMapCoordinates,
text: String, text: String,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
@ -203,7 +212,7 @@ public fun MapFeatureBuilder.text(
id: FeatureId? = null, id: FeatureId? = null,
): FeatureId = addFeature(id, MapTextFeature(position, text, zoomRange, color, font)) ): FeatureId = addFeature(id, MapTextFeature(position, text, zoomRange, color, font))
public fun MapFeatureBuilder.text( public fun MapFeaturesState.text(
position: Pair<Double, Double>, position: Pair<Double, Double>,
text: String, text: String,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,

View File

@ -68,7 +68,7 @@ public data class MapViewConfig(
public expect fun MapView( public expect fun MapView(
mapTileProvider: MapTileProvider, mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint, initialViewPoint: MapViewPoint,
features: Map<FeatureId, MapFeature>, features: MapFeaturesState,
config: MapViewConfig = MapViewConfig(), config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(), modifier: Modifier = Modifier.fillMaxSize(),
) )
@ -103,13 +103,12 @@ public fun MapView(
initialRectangle: GmcRectangle? = null, initialRectangle: GmcRectangle? = null,
config: MapViewConfig = MapViewConfig(), config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(), modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {}, buildFeatures: @Composable (MapFeaturesState.() -> Unit) = {},
): Unit = key(buildFeatures) { ): Unit = key(buildFeatures) {
val featuresBuilder = MapFeatureBuilderImpl().apply { buildFeatures() } val featureState = rememberMapFeatureState(buildFeatures)
val features = featuresBuilder.features val features = featureState.features()
val attributes = featuresBuilder.attributes
var cachedCanvasSize: DpSize by remember { mutableStateOf(defaultCanvasSize) } var cachedCanvasSize: DpSize by remember { mutableStateOf(defaultCanvasSize) }
@ -120,36 +119,37 @@ public fun MapView(
?: MapViewPoint.globe ?: MapViewPoint.globe
} }
val featureDrag = DragHandle.withPrimaryButton { _, start, end -> val featureDrag by derivedStateOf {
val zoom = start.zoom DragHandle.withPrimaryButton { _, start, end ->
attributes.filterValues { val zoom = start.zoom
it[DraggableAttribute] as? Boolean ?: false featureState.findAllWithAttribute(DraggableAttribute) { it }.forEach { id ->
}.keys.forEach { id -> val feature = features[id] as? DraggableMapFeature ?: return@forEach
val feature = features[id] as? DraggableMapFeature ?: return@forEach val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach
//val border = WebMercatorProjection.scaleFactor(zoom) if (start.focus in boundingBox) {
val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach featureState.addFeature(id, feature.withCoordinates(end.focus))
if (start.focus in boundingBox) { return@withPrimaryButton false
features[id] = feature.withCoordinates(end.focus) }
return@withPrimaryButton false
} }
return@withPrimaryButton true
} }
return@withPrimaryButton true
} }
val newConfig = config.copy( val newConfig by derivedStateOf {
dragHandle = DragHandle.combine(featureDrag, config.dragHandle), config.copy(
onCanvasSizeChange = { canvasSize -> dragHandle = DragHandle.combine(featureDrag, config.dragHandle),
config.onCanvasSizeChange(canvasSize) onCanvasSizeChange = { canvasSize ->
cachedCanvasSize = canvasSize config.onCanvasSizeChange(canvasSize)
} cachedCanvasSize = canvasSize
) }
)
}
MapView( MapView(
mapTileProvider = mapTileProvider, mapTileProvider = mapTileProvider,
initialViewPoint = viewPointOverride, initialViewPoint = viewPointOverride,
features = features, features = featureState,
config = newConfig, config = newConfig,
modifier = modifier, modifier = modifier,
) )

View File

@ -1,3 +0,0 @@
package center.sciprog.maps.compose
public object DraggableAttribute: MapFeatureAttributeKey<Boolean>

View File

@ -50,7 +50,7 @@ private val logger = KotlinLogging.logger("MapView")
public actual fun MapView( public actual fun MapView(
mapTileProvider: MapTileProvider, mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint, initialViewPoint: MapViewPoint,
features: Map<FeatureId, MapFeature>, features: MapFeaturesState,
config: MapViewConfig, config: MapViewConfig,
modifier: Modifier, modifier: Modifier,
): Unit = key(initialViewPoint) { ): Unit = key(initialViewPoint) {
@ -334,7 +334,7 @@ public actual fun MapView(
dstSize = tileSize dstSize = tileSize
) )
} }
features.values.filter { zoom in it.zoomRange }.forEach { feature -> features.features().values.filter { zoom in it.zoomRange }.forEach { feature ->
drawFeature(zoom, feature) drawFeature(zoom, feature)
} }
} }