Fix draggable

This commit is contained in:
Alexander Nozik 2022-09-14 12:11:15 +03:00
parent 693f608b14
commit 8c79c913e6
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
4 changed files with 60 additions and 62 deletions

View File

@ -5,8 +5,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.VectorPainter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntSize
@ -136,23 +136,18 @@ public class MapBitmapImageFeature(
public class MapVectorImageFeature(
public val position: GeodeticMapCoordinates,
public val painter: Painter,
public val size: DpSize,
public val image: ImageVector,
public val size: DpSize = DpSize(20.dp, 20.dp),
override val zoomRange: IntRange = defaultZoomRange,
) : DraggableMapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
MapVectorImageFeature(newCoordinates, painter, size, zoomRange)
}
MapVectorImageFeature(newCoordinates, image, size, zoomRange)
@Composable
public fun MapVectorImageFeature(
position: GeodeticMapCoordinates,
image: ImageVector,
size: DpSize = DpSize(20.dp, 20.dp),
zoomRange: IntRange = defaultZoomRange,
): MapVectorImageFeature = MapVectorImageFeature(position, rememberVectorPainter(image), size, zoomRange)
@Composable
public fun painter(): VectorPainter = rememberVectorPainter(image)
}
/**
* A group of other features

View File

@ -38,16 +38,16 @@ public class MapFeaturesState internal constructor(
return safeId
}
public fun <T> setAttribute(id: FeatureId, key: MapFeaturesState.Attribute<T>, value: T) {
public fun <T> setAttribute(id: FeatureId, key: Attribute<T>, value: T) {
attributes.getOrPut(id) { mutableStateMapOf() }[key] = value
}
@Suppress("UNCHECKED_CAST")
public fun <T> getAttribute(id: FeatureId, key: MapFeaturesState.Attribute<T>): T? =
public fun <T> getAttribute(id: FeatureId, key: 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> {
public fun <T> findAllWithAttribute(key: Attribute<T>, condition: (T) -> Boolean): Set<FeatureId> {
return attributes.filterValues {
condition(it[key] as T)
}.keys
@ -56,13 +56,13 @@ public class MapFeaturesState internal constructor(
@Composable
public fun rememberMapFeatureState(
builder: @Composable MapFeaturesState.() -> Unit = {},
builder: MapFeaturesState.() -> Unit = {},
): MapFeaturesState = remember(builder) {
MapFeaturesState(
mutableStateMapOf(),
mutableStateMapOf()
)
}.apply { builder() }
).apply(builder)
}
public fun MapFeaturesState.circle(
center: GeodeticMapCoordinates,
@ -183,7 +183,6 @@ public fun MapFeaturesState.points(
id: FeatureId? = null,
): FeatureId = addFeature(id, MapPointsFeature(points.map { it.toCoordinates() }, zoomRange, stroke, color, pointMode))
@Composable
public fun MapFeaturesState.image(
position: Pair<Double, Double>,
image: ImageVector,
@ -192,13 +191,15 @@ public fun MapFeaturesState.image(
id: FeatureId? = null,
): FeatureId = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange))
@Composable
public fun MapFeaturesState.group(
zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null,
builder: @Composable MapFeaturesState.() -> Unit,
builder: MapFeaturesState.() -> Unit,
): FeatureId {
val map = rememberMapFeatureState(builder).features()
val map = MapFeaturesState(
mutableStateMapOf(),
mutableStateMapOf()
).apply(builder).features()
val feature = MapFeatureGroup(map, zoomRange)
return addFeature(id, feature)
}

View File

@ -87,7 +87,7 @@ internal fun GmcRectangle.computeViewPoint(
return MapViewPoint(center, zoom)
}
private val defaultCanvasSize = DpSize(512.dp, 512.dp)
internal val defaultCanvasSize = DpSize(512.dp, 512.dp)
/**
* Draw a map using convenient parameters. If neither [initialViewPoint], noe [initialRectangle] is defined,
@ -103,46 +103,44 @@ public fun MapView(
initialRectangle: GmcRectangle? = null,
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (MapFeaturesState.() -> Unit) = {},
buildFeatures: MapFeaturesState.() -> Unit = {},
): Unit = key(buildFeatures) {
val featureState = rememberMapFeatureState(buildFeatures)
val features = featureState.features()
var cachedCanvasSize: DpSize by remember { mutableStateOf(defaultCanvasSize) }
val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle, cachedCanvasSize) {
val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) {
initialViewPoint
?: initialRectangle?.computeViewPoint(mapTileProvider, cachedCanvasSize)
?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider, cachedCanvasSize)
?: initialRectangle?.computeViewPoint(mapTileProvider, defaultCanvasSize)
?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider, defaultCanvasSize)
?: MapViewPoint.globe
}
val featureDrag by 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
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
}
return@withPrimaryButton true
}
}
val newConfig by derivedStateOf {
config.copy(
dragHandle = DragHandle.combine(featureDrag, config.dragHandle),
onCanvasSizeChange = { canvasSize ->
config.onCanvasSizeChange(canvasSize)
cachedCanvasSize = canvasSize
}
)
val newConfig by remember {
derivedStateOf {
config.copy(
dragHandle = DragHandle.combine(featureDrag, config.dragHandle)
)
}
}

View File

@ -45,7 +45,6 @@ private val logger = KotlinLogging.logger("MapView")
/**
* A component that renders map and provides basic map manipulation capabilities
*/
@Composable
public actual fun MapView(
mapTileProvider: MapTileProvider,
@ -54,10 +53,12 @@ public actual fun MapView(
config: MapViewConfig,
modifier: Modifier,
): Unit = key(initialViewPoint) {
var canvasSize by remember { mutableStateOf(DpSize(512.dp, 512.dp)) }
var canvasSize by remember { mutableStateOf(defaultCanvasSize) }
var viewPoint by remember { mutableStateOf(initialViewPoint) }
require(viewPoint.zoom in 1.0..18.0) { "Zoom value of ${viewPoint.zoom} is not valid" }
fun setViewPoint(newViewPoint: MapViewPoint) {
config.onViewChange(newViewPoint)
viewPoint = newViewPoint
@ -68,9 +69,7 @@ public actual fun MapView(
floor(viewPoint.zoom).toInt()
}
val tileScale: Double by derivedStateOf {
2.0.pow(viewPoint.zoom - zoom)
}
val tileScale: Double by derivedStateOf { 2.0.pow(viewPoint.zoom - zoom) }
val mapTiles = remember(mapTileProvider) { mutableStateListOf<MapTile>() }
@ -103,9 +102,12 @@ public actual fun MapView(
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
//start selection
if (event.buttons.isPrimaryPressed && event.keyboardModifiers.isShiftPressed) {
selectRect = Rect(change.position, change.position)
}
var selectionStart: Offset? =
if (event.buttons.isPrimaryPressed && event.keyboardModifiers.isShiftPressed) {
change.position
} else {
null
}
drag(change.id) { dragChange ->
val dragAmount = dragChange.position - dragChange.previousPosition
@ -124,19 +126,19 @@ public actual fun MapView(
)
) {
//clear selection just in case
selectRect = null
selectionStart = null
return@drag
}
if (event.buttons.isPrimaryPressed) {
//Evaluating selection frame
selectRect?.let { rect ->
selectionStart?.let { start ->
val offset = dragChange.position
selectRect = Rect(
min(offset.x, rect.left),
min(offset.y, rect.top),
max(offset.x, rect.right),
max(offset.y, rect.bottom)
min(offset.x, start.x),
min(offset.y, start.y),
max(offset.x, start.x),
max(offset.y, start.y)
)
return@drag
}
@ -214,6 +216,8 @@ public actual fun MapView(
}
}
val painterCache = features.features().values.filterIsInstance<MapVectorImageFeature>().associateWith { it.painter() }
Canvas(canvasModifier) {
fun WebMercatorCoordinates.toOffset(): Offset = Offset(
@ -265,7 +269,7 @@ public actual fun MapView(
val offset = feature.position.toOffset()
val size = feature.size.toSize()
translate(offset.x - size.width / 2, offset.y - size.height / 2) {
with(feature.painter) {
with(painterCache[feature]!!) {
draw(size)
}
}