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 {
group = "center.sciprog"
version = "0.1.0-dev-5"
version = "0.1.0-dev-6"
}
ksciencePublish{

View File

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

View File

@ -1,7 +1,9 @@
package center.sciprog.maps.compose
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.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.isPrimaryPressed
@ -68,15 +70,16 @@ public data class MapViewConfig(
public expect fun MapView(
mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint,
features: MapFeaturesState,
featuresState: MapFeaturesState,
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
)
internal val defaultCanvasSize = DpSize(512.dp, 512.dp)
internal fun GmcRectangle.computeViewPoint(
public fun GmcRectangle.computeViewPoint(
mapTileProvider: MapTileProvider,
canvasSize: DpSize,
canvasSize: DpSize = defaultCanvasSize,
): MapViewPoint {
val zoom = log2(
min(
@ -87,7 +90,33 @@ internal fun GmcRectangle.computeViewPoint(
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,
@ -104,50 +133,40 @@ public fun MapView(
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: MapFeaturesState.() -> Unit = {},
): Unit = key(buildFeatures) {
val featureState = rememberMapFeatureState(buildFeatures)
) {
val featureState = MapFeaturesState.remember(buildFeatures)
val features = featureState.features()
val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) {
initialViewPoint
?: initialRectangle?.computeViewPoint(mapTileProvider, defaultCanvasSize)
?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider, defaultCanvasSize)
?: initialRectangle?.computeViewPoint(mapTileProvider)
?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider)
?: MapViewPoint.globe
}
val featureDrag by remember {
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
val featureDrag = 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
}
val newConfig by remember {
derivedStateOf {
config.copy(
dragHandle = DragHandle.combine(featureDrag, config.dragHandle)
)
}
}
val newConfig = config.copy(
dragHandle = DragHandle.combine(featureDrag, config.dragHandle)
)
MapView(
mapTileProvider = mapTileProvider,
initialViewPoint = viewPointOverride,
features = featureState,
featuresState = featureState,
config = newConfig,
modifier = modifier,
)

View File

@ -49,7 +49,7 @@ private val logger = KotlinLogging.logger("MapView")
public actual fun MapView(
mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint,
features: MapFeaturesState,
featuresState: MapFeaturesState,
config: MapViewConfig,
modifier: Modifier,
): 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) {
fun WebMercatorCoordinates.toOffset(): Offset = Offset(
@ -338,10 +338,12 @@ public actual fun MapView(
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)
}
}
selectRect?.let { rect ->
drawRect(
color = Color.Blue,