167 lines
5.5 KiB
Kotlin
Raw Normal View History

2022-07-23 10:58:16 +03:00
package center.sciprog.maps.compose
2022-07-11 09:36:43 +03:00
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
2022-07-11 09:36:43 +03:00
import androidx.compose.ui.Modifier
2022-08-30 10:29:56 +03:00
import androidx.compose.ui.input.pointer.PointerEvent
2022-08-31 22:48:52 +03:00
import androidx.compose.ui.input.pointer.isPrimaryPressed
2022-07-14 20:19:57 +03:00
import androidx.compose.ui.unit.DpSize
2022-07-23 11:24:37 +03:00
import center.sciprog.maps.coordinates.*
2022-07-14 20:19:57 +03:00
import kotlin.math.PI
import kotlin.math.log2
import kotlin.math.min
2022-07-11 09:36:43 +03:00
2022-07-13 11:36:02 +03:00
2022-08-30 10:29:56 +03:00
public fun interface DragHandle {
/**
* @param event - qualifiers of the event used for drag
* @param start - is a point where drag begins, end is a point where drag ends
* @param end - end point of the drag
*
* @return true if default event processors should be used after this one
*/
2022-08-31 22:48:52 +03:00
public fun handle(event: PointerEvent, start: MapViewPoint, end: MapViewPoint): Boolean
2022-08-30 10:29:56 +03:00
public companion object {
public val BYPASS: DragHandle = DragHandle { _, _, _ -> true }
2022-08-31 22:48:52 +03:00
/**
* Process only events with primary button pressed
*/
public fun withPrimaryButton(
block: (event: PointerEvent, start: MapViewPoint, end: MapViewPoint) -> Boolean,
): DragHandle = DragHandle { event, start, end ->
if (event.buttons.isPrimaryPressed) {
block(event, start, end)
} else {
false
}
}
/**
* Combine several handles into one
*/
public fun combine(vararg handles: DragHandle): DragHandle = DragHandle { event, start, end ->
handles.forEach {
if (!it.handle(event, start, end)) return@DragHandle false
}
return@DragHandle true
}
2022-08-30 10:29:56 +03:00
}
}
2022-07-19 12:31:09 +03:00
//TODO consider replacing by modifier
/**
*/
2022-07-23 13:49:47 +03:00
public data class MapViewConfig(
2022-07-13 11:36:02 +03:00
val zoomSpeed: Double = 1.0 / 3.0,
2022-08-30 10:29:56 +03:00
val onClick: MapViewPoint.(PointerEvent) -> Unit = {},
val dragHandle: DragHandle = DragHandle.BYPASS,
2022-07-19 12:31:09 +03:00
val onViewChange: MapViewPoint.() -> Unit = {},
2022-08-29 13:38:46 +03:00
val onSelect: (GmcRectangle) -> Unit = {},
val zoomOnSelect: Boolean = true,
2022-07-13 11:36:02 +03:00
)
2022-07-11 09:36:43 +03:00
@Composable
2022-07-23 13:49:47 +03:00
public expect fun MapView(
2022-07-11 09:36:43 +03:00
mapTileProvider: MapTileProvider,
2022-07-14 20:19:57 +03:00
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
2022-07-11 18:43:05 +03:00
features: Map<FeatureId, MapFeature>,
2022-07-13 11:36:02 +03:00
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
)
2022-08-31 22:48:52 +03:00
private fun prepareConfig(initialConfig: MapViewConfig, featureBuilder: MapFeatureBuilder): MapViewConfig {
val draggableFeatureIds: Set<FeatureId> = featureBuilder.attributes().filterValues {
it[DraggableAttribute] ?: false
}.keys
val features = featureBuilder.features
val featureDrag = DragHandle.withPrimaryButton { _, start, end ->
val zoom = start.zoom
draggableFeatureIds.forEach { id ->
val feature = features[id] as? DraggableMapFeature ?: return@forEach
//val border = WebMercatorProjection.scaleFactor(zoom)
val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach
if (start.focus in boundingBox) {
features[id] = feature.withCoordinates(end.focus)
return@withPrimaryButton false
}
}
return@withPrimaryButton true
}
return initialConfig.copy(
dragHandle = DragHandle.combine(featureDrag, initialConfig.dragHandle),
)
}
internal fun GmcRectangle.computeViewPoint(
mapTileProvider: MapTileProvider,
): (canvasSize: DpSize) -> MapViewPoint = { canvasSize ->
val zoom = log2(
min(
canvasSize.width.value / longitudeDelta.radians.value,
canvasSize.height.value / latitudeDelta.radians.value
) * PI / mapTileProvider.tileSize
)
MapViewPoint(center, zoom)
}
@Composable
2022-07-23 13:49:47 +03:00
public fun MapView(
2022-07-14 20:19:57 +03:00
mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint? = null,
2022-07-14 20:19:57 +03:00
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
2022-07-23 13:49:47 +03:00
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
2022-07-14 20:19:57 +03:00
) {
2022-08-31 22:48:52 +03:00
val featuresBuilder = MapFeatureBuilderImpl(mutableStateMapOf())
2022-07-14 20:19:57 +03:00
featuresBuilder.buildFeatures()
2022-08-31 22:48:52 +03:00
val features = remember { featuresBuilder.features }
val newConfig = remember(features) {
prepareConfig(config, featuresBuilder)
}
2022-07-19 12:31:09 +03:00
MapView(
mapTileProvider,
{ canvasSize ->
initialViewPoint ?: features.values.computeBoundingBox(1.0)?.let { box ->
val zoom = log2(
min(
canvasSize.width.value / box.longitudeDelta.radians.value,
canvasSize.height.value / box.latitudeDelta.radians.value
) * PI / mapTileProvider.tileSize
)
MapViewPoint(box.center, zoom)
} ?: MapViewPoint(GeodeticMapCoordinates(0.0.radians, 0.0.radians), 1.0)
},
2022-08-31 22:48:52 +03:00
features,
newConfig,
2022-07-19 12:31:09 +03:00
modifier
)
}
2022-08-31 22:48:52 +03:00
//
//@Composable
//public fun MapView(
// mapTileProvider: MapTileProvider,
// box: GmcRectangle,
// config: MapViewConfig = MapViewConfig(),
// modifier: Modifier = Modifier.fillMaxSize(),
// buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
//) {
// val builder by derivedStateOf { MapFeatureBuilderImpl().apply(buildFeatures) }
//
// MapView(
// mapTileProvider,
// box.computeViewPoint(mapTileProvider),
// builder.features,
// prepareConfig(config, builder),
// modifier
// )
//}