Add separate builders for feature state

This commit is contained in:
Alexander Nozik 2022-09-14 15:09:02 +03:00
parent 8c79c913e6
commit 2b01b8e316
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
4 changed files with 80 additions and 46 deletions

View File

@ -10,7 +10,7 @@ val ktorVersion by extra("2.0.3")
allprojects { allprojects {
group = "center.sciprog" group = "center.sciprog"
version = "0.1.0-dev-5" version = "0.1.0-dev-6"
} }
ksciencePublish{ ksciencePublish{

View File

@ -52,16 +52,29 @@ public class MapFeaturesState internal constructor(
condition(it[key] as T) condition(it[key] as T)
}.keys }.keys
} }
} public companion object{
@Composable /**
public fun rememberMapFeatureState( * Build, but do not remember map feature state
builder: MapFeaturesState.() -> Unit = {}, */
): MapFeaturesState = remember(builder) { public fun build(
MapFeaturesState( builder: MapFeaturesState.() -> Unit = {},
mutableStateMapOf(), ): MapFeaturesState = MapFeaturesState(
mutableStateMapOf() mutableStateMapOf(),
).apply(builder) 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( public fun MapFeaturesState.circle(

View File

@ -1,7 +1,9 @@
package center.sciprog.maps.compose package center.sciprog.maps.compose
import androidx.compose.foundation.layout.fillMaxSize 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.Modifier
import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.isPrimaryPressed import androidx.compose.ui.input.pointer.isPrimaryPressed
@ -68,15 +70,16 @@ public data class MapViewConfig(
public expect fun MapView( public expect fun MapView(
mapTileProvider: MapTileProvider, mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint, initialViewPoint: MapViewPoint,
features: MapFeaturesState, featuresState: MapFeaturesState,
config: MapViewConfig = MapViewConfig(), config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(), modifier: Modifier = Modifier.fillMaxSize(),
) )
internal val defaultCanvasSize = DpSize(512.dp, 512.dp)
internal fun GmcRectangle.computeViewPoint( public fun GmcRectangle.computeViewPoint(
mapTileProvider: MapTileProvider, mapTileProvider: MapTileProvider,
canvasSize: DpSize, canvasSize: DpSize = defaultCanvasSize,
): MapViewPoint { ): MapViewPoint {
val zoom = log2( val zoom = log2(
min( min(
@ -87,7 +90,33 @@ internal fun GmcRectangle.computeViewPoint(
return MapViewPoint(center, zoom) 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<FeatureId, MapFeature>,
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, * Draw a map using convenient parameters. If neither [initialViewPoint], noe [initialRectangle] is defined,
@ -104,50 +133,40 @@ public fun MapView(
config: MapViewConfig = MapViewConfig(), config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(), modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: MapFeaturesState.() -> Unit = {}, buildFeatures: MapFeaturesState.() -> Unit = {},
): Unit = key(buildFeatures) { ) {
val featureState = MapFeaturesState.remember(buildFeatures)
val featureState = rememberMapFeatureState(buildFeatures)
val features = featureState.features() val features = featureState.features()
val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) { val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) {
initialViewPoint initialViewPoint
?: initialRectangle?.computeViewPoint(mapTileProvider, defaultCanvasSize) ?: initialRectangle?.computeViewPoint(mapTileProvider)
?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider, defaultCanvasSize) ?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider)
?: MapViewPoint.globe ?: MapViewPoint.globe
} }
val featureDrag by remember { val featureDrag = DragHandle.withPrimaryButton { _, start, end ->
derivedStateOf { val zoom = start.zoom
DragHandle.withPrimaryButton { _, start, end -> featureState.findAllWithAttribute(DraggableAttribute) { it }.forEach { id ->
val zoom = start.zoom val feature = features[id] as? DraggableMapFeature ?: return@forEach
featureState.findAllWithAttribute(DraggableAttribute) { it }.forEach { id -> val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach
val feature = features[id] as? DraggableMapFeature ?: return@forEach 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
featureState.addFeature(id, feature.withCoordinates(end.focus))
return@withPrimaryButton false
}
}
return@withPrimaryButton true
} }
} }
return@withPrimaryButton true
} }
val newConfig by remember { val newConfig = config.copy(
derivedStateOf { dragHandle = DragHandle.combine(featureDrag, config.dragHandle)
config.copy( )
dragHandle = DragHandle.combine(featureDrag, config.dragHandle)
)
}
}
MapView( MapView(
mapTileProvider = mapTileProvider, mapTileProvider = mapTileProvider,
initialViewPoint = viewPointOverride, initialViewPoint = viewPointOverride,
features = featureState, featuresState = featureState,
config = newConfig, config = newConfig,
modifier = modifier, modifier = modifier,
) )

View File

@ -49,7 +49,7 @@ private val logger = KotlinLogging.logger("MapView")
public actual fun MapView( public actual fun MapView(
mapTileProvider: MapTileProvider, mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint, initialViewPoint: MapViewPoint,
features: MapFeaturesState, featuresState: MapFeaturesState,
config: MapViewConfig, config: MapViewConfig,
modifier: Modifier, modifier: Modifier,
): Unit = key(initialViewPoint) { ): Unit = key(initialViewPoint) {
@ -216,8 +216,8 @@ public actual fun MapView(
} }
} }
val painterCache = features.features().values.filterIsInstance<MapVectorImageFeature>().associateWith { it.painter() } val painterCache = featuresState.features().values.filterIsInstance<MapVectorImageFeature>()
.associateWith { it.painter() }
Canvas(canvasModifier) { Canvas(canvasModifier) {
fun WebMercatorCoordinates.toOffset(): Offset = Offset( fun WebMercatorCoordinates.toOffset(): Offset = Offset(
@ -338,10 +338,12 @@ public actual fun MapView(
dstSize = tileSize 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) drawFeature(zoom, feature)
} }
} }
selectRect?.let { rect -> selectRect?.let { rect ->
drawRect( drawRect(
color = Color.Blue, color = Color.Blue,