diff --git a/demo/maps/src/jvmMain/kotlin/Main.kt b/demo/maps/src/jvmMain/kotlin/Main.kt index 6bf40a6..dbcba0a 100644 --- a/demo/maps/src/jvmMain/kotlin/Main.kt +++ b/demo/maps/src/jvmMain/kotlin/Main.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PointMode +import androidx.compose.ui.input.pointer.isPrimaryPressed import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import center.sciprog.maps.compose.* @@ -58,14 +59,18 @@ fun App() { config = MapViewConfig( inferViewBoxFromFeatures = true, onViewChange = { centerCoordinates = focus }, - onDrag = { start, end -> - if (start.focus.latitude.degrees.value in (pointTwo.first - 0.05)..(pointTwo.first + 0.05) && + dragHandle = { event, start, end -> + if (!event.buttons.isPrimaryPressed) { + true + } else if (start.focus.latitude.degrees.value in (pointTwo.first - 0.05)..(pointTwo.first + 0.05) && start.focus.longitude.degrees.value in (pointTwo.second - 0.05)..(pointTwo.second + 0.05) ) { pointTwo = pointTwo.first + (end.focus.latitude - start.focus.latitude).degrees.value to pointTwo.second + (end.focus.longitude - start.focus.longitude).degrees.value false// returning false, because when we are dragging circle we don't want to drag map - } else true + } else { + true + } } ) ) { diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt index 3771850..2829891 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt @@ -3,6 +3,7 @@ package center.sciprog.maps.compose import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.unit.DpSize import center.sciprog.maps.coordinates.* import kotlin.math.PI @@ -10,16 +11,29 @@ import kotlin.math.log2 import kotlin.math.min +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 + */ + public fun drag(event: PointerEvent, start: MapViewPoint, end: MapViewPoint): Boolean + + public companion object { + public val BYPASS: DragHandle = DragHandle { _, _, _ -> true } + } +} + //TODO consider replacing by modifier /** - * @param onDrag - returns true if you want to drag a map and false, if you want to make map stationary. - * start - is a point where drag begins, end is a point where drag ends */ public data class MapViewConfig( val zoomSpeed: Double = 1.0 / 3.0, val inferViewBoxFromFeatures: Boolean = false, - val onClick: MapViewPoint.() -> Unit = {}, - val onDrag: (start: MapViewPoint, end: MapViewPoint) -> Boolean = { _, _ -> true }, + val onClick: MapViewPoint.(PointerEvent) -> Unit = {}, + val dragHandle: DragHandle = DragHandle.BYPASS, val onViewChange: MapViewPoint.() -> Unit = {}, val onSelect: (GmcRectangle) -> Unit = {}, val zoomOnSelect: Boolean = true, diff --git a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt index 09c2bb3..3dfdadd 100644 --- a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt +++ b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt @@ -80,9 +80,14 @@ public actual fun MapView( } } - val zoom: Int by derivedStateOf { floor(viewPoint.zoom).toInt() } + val zoom: Int by derivedStateOf { + require(viewPoint.zoom in 1.0..18.0) { "Zoom value of ${viewPoint.zoom} is not valid" } + 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 { mutableStateListOf() } @@ -109,61 +114,75 @@ public actual fun MapView( fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp()) val event: PointerEvent = awaitPointerEvent() - event.changes.forEach { change -> - if (event.buttons.isPrimaryPressed) { - //Evaluating selection frame - if (event.keyboardModifiers.isShiftPressed) { - selectRect = Rect(change.position, change.position) - drag(change.id) { dragChange -> - selectRect?.let { rect -> - 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) - ) - } - } - selectRect?.let { rect -> - //Use selection override if it is defined - val gmcBox = GmcRectangle( - rect.topLeft.toDpOffset().toGeodetic(), - rect.bottomRight.toDpOffset().toGeodetic() - ) - config.onSelect(gmcBox) - if (config.zoomOnSelect) { - val newViewPoint = gmcBox.computeViewPoint(mapTileProvider).invoke(canvasSize) - config.onViewChange(newViewPoint) - viewPointInternal = newViewPoint - } - selectRect = null - } - } else { - val dragStart = change.position - val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp()) - config.onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom)) - drag(change.id) { dragChange -> - val dragAmount = dragChange.position - dragChange.previousPosition - val dpStart = DpOffset( - dragChange.previousPosition.x.toDp(), - dragChange.previousPosition.y.toDp() - ) - val dpEnd = DpOffset(dragChange.position.x.toDp(), dragChange.position.y.toDp()) - if (!config.onDrag( - MapViewPoint(dpStart.toGeodetic(), viewPoint.zoom), - MapViewPoint(dpEnd.toGeodetic(), viewPoint.zoom) - ) - ) return@drag - val newViewPoint = viewPoint.move( - -dragAmount.x.toDp().value / tileScale, - +dragAmount.y.toDp().value / tileScale - ) - config.onViewChange(newViewPoint) - viewPointInternal = newViewPoint - } + event.changes.forEach { change -> + val dragStart = change.position + val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp()) + + //start selection + if (event.buttons.isPrimaryPressed && event.keyboardModifiers.isShiftPressed) { + selectRect = Rect(change.position, change.position) + } + + drag(change.id) { dragChange -> + val dragAmount = dragChange.position - dragChange.previousPosition + val dpStart = DpOffset( + dragChange.previousPosition.x.toDp(), + dragChange.previousPosition.y.toDp() + ) + val dpEnd = DpOffset(dragChange.position.x.toDp(), dragChange.position.y.toDp()) + + if ( + !config.dragHandle.drag( + event, + MapViewPoint(dpStart.toGeodetic(), viewPoint.zoom), + MapViewPoint(dpEnd.toGeodetic(), viewPoint.zoom) + ) + ) { + //clear selection just in case + selectRect = null + return@drag } + + if (event.buttons.isPrimaryPressed) { + //Evaluating selection frame + selectRect?.let { rect -> + 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) + ) + return@drag + } + + config.onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom), event) + + val newViewPoint = viewPoint.move( + -dragAmount.x.toDp().value / tileScale, + +dragAmount.y.toDp().value / tileScale + ) + config.onViewChange(newViewPoint) + viewPointInternal = newViewPoint + } + } + + // evaluate selection + selectRect?.let { rect -> + //Use selection override if it is defined + val gmcBox = GmcRectangle( + rect.topLeft.toDpOffset().toGeodetic(), + rect.bottomRight.toDpOffset().toGeodetic() + ) + config.onSelect(gmcBox) + if (config.zoomOnSelect) { + val newViewPoint = gmcBox.computeViewPoint(mapTileProvider).invoke(canvasSize) + + config.onViewChange(newViewPoint) + viewPointInternal = newViewPoint + } + selectRect = null } } }