diff --git a/demo/maps/src/jvmMain/kotlin/Main.kt b/demo/maps/src/jvmMain/kotlin/Main.kt index 2b63878..c012842 100644 --- a/demo/maps/src/jvmMain/kotlin/Main.kt +++ b/demo/maps/src/jvmMain/kotlin/Main.kt @@ -1,4 +1,6 @@ import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.PointerMatcher import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Home @@ -34,6 +36,7 @@ private fun GeodeticMapCoordinates.toShortString(): String = "${(latitude.degrees.value).toString().take(6)}:${(longitude.degrees.value).toString().take(6)}" +@OptIn(ExperimentalFoundationApi::class) @Composable @Preview fun App() { @@ -120,9 +123,9 @@ fun App() { } }.launchIn(scope) - + //Add click listeners for all polygons forEachWithType> { id, feature -> - id.onClick { + id.onClick(PointerMatcher.Primary) { println("Click on $id") //draw in top-level scope with(this@MapView) { diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/CoordinateViewScope.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/CoordinateViewScope.kt index 8a2afaa..03631ec 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/CoordinateViewScope.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/CoordinateViewScope.kt @@ -37,6 +37,7 @@ public abstract class CoordinateViewScope( public abstract fun DpOffset.toCoordinates(): T + public abstract fun T.toDpOffset(): DpOffset public fun T.toOffset(density: Density): Offset = with(density) { @@ -44,6 +45,11 @@ public abstract class CoordinateViewScope( Offset(dpOffset.x.toPx(), dpOffset.y.toPx()) } + public fun Offset.toCoordinates(density: Density): T = with(density) { + val dpOffset = DpOffset(x.toDp(), y.toDp()) + dpOffset.toCoordinates() + } + public abstract fun Rectangle.toDpRect(): DpRect public abstract fun ViewPoint.moveBy(x: Dp, y: Dp): ViewPoint diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt index 2e44ee4..ba42c77 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt @@ -1,5 +1,7 @@ package center.sciprog.maps.features +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.PointerMatcher import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.remember @@ -10,6 +12,7 @@ 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.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.PointerKeyboardModifiers import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp @@ -144,13 +147,30 @@ public data class FeatureGroup( ) } - @Suppress("UNCHECKED_CAST") public fun > FeatureId.onClick( onClick: PointerEvent.(click: ViewPoint) -> Unit, ): FeatureId = modifyAttributes { ClickListenerAttribute.add( - MouseListener { event, point -> event.onClick(point as ViewPoint) } + MouseListener { event, point -> + event.onClick(point as ViewPoint) + } + ) + } + + @OptIn(ExperimentalFoundationApi::class) + @Suppress("UNCHECKED_CAST") + public fun > FeatureId.onClick( + pointerMatcher: PointerMatcher, + keyboardModifiers: PointerKeyboardModifiers.() -> Boolean = {true}, + onClick: PointerEvent.(click: ViewPoint) -> Unit, + ): FeatureId = modifyAttributes { + ClickListenerAttribute.add( + MouseListener { event, point -> + if (pointerMatcher.matches(event) && keyboardModifiers(event.keyboardModifiers)) { + event.onClick(point as ViewPoint) + } + } ) } @@ -163,6 +183,18 @@ public data class FeatureGroup( ) } +// @Suppress("UNCHECKED_CAST") +// @OptIn(ExperimentalFoundationApi::class) +// public fun > FeatureId.onTap( +// pointerMatcher: PointerMatcher = PointerMatcher.Primary, +// keyboardFilter: PointerKeyboardModifiers.() -> Boolean = { true }, +// onTap: (point: ViewPoint) -> Unit, +// ): FeatureId = modifyAttributes { +// TapListenerAttribute.add( +// TapListener(pointerMatcher, keyboardFilter) { point -> onTap(point as ViewPoint) } +// ) +// } + public fun > FeatureId.color(color: Color): FeatureId = modifyAttribute(ColorAttribute, color) @@ -187,7 +219,7 @@ public data class FeatureGroup( public fun remember( coordinateSpace: CoordinateSpace, builder: FeatureGroup.() -> Unit = {}, - ): FeatureGroup = remember{ + ): FeatureGroup = remember { build(coordinateSpace, builder) } diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/MouseListener.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/MouseListener.kt new file mode 100644 index 0000000..dc66b02 --- /dev/null +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/MouseListener.kt @@ -0,0 +1,25 @@ +package center.sciprog.maps.features + +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.isPrimaryPressed + +public fun interface MouseListener { + public fun handle(event: PointerEvent, point: ViewPoint): Unit + + public companion object { + public fun withPrimaryButton( + block: (event: PointerEvent, click: ViewPoint) -> Unit, + ): MouseListener = MouseListener { event, click -> + if (event.buttons.isPrimaryPressed) { + block(event, click) + } + } + } +} + +//@OptIn(ExperimentalFoundationApi::class) +//public class TapListener( +// public val pointerMatcher: PointerMatcher, +// public val keyboardFilter: PointerKeyboardModifiers.() -> Boolean = { true }, +// public val onTap: (point: ViewPoint) -> Unit, +//) \ No newline at end of file diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/ViewConfig.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/ViewConfig.kt index 7c91f37..2108cb0 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/ViewConfig.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/ViewConfig.kt @@ -1,23 +1,7 @@ package center.sciprog.maps.features -import androidx.compose.ui.input.pointer.PointerEvent -import androidx.compose.ui.input.pointer.isPrimaryPressed import androidx.compose.ui.unit.DpSize -public fun interface MouseListener { - public fun handle(event: PointerEvent, point: ViewPoint): Unit - - public companion object { - public fun withPrimaryButton( - block: (event: PointerEvent, click: ViewPoint) -> Unit, - ): MouseListener = MouseListener { event, click -> - if (event.buttons.isPrimaryPressed) { - block(event, click) - } - } - } -} - public data class ViewConfig( val zoomSpeed: Float = 1f / 3f, val onClick: MouseListener? = null, diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/mapFeatureAttributes.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/mapFeatureAttributes.kt index 7726a18..d66e7a4 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/mapFeatureAttributes.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/mapFeatureAttributes.kt @@ -14,6 +14,8 @@ public object ClickListenerAttribute : SetAttribute> public object HoverListenerAttribute : SetAttribute> +//public object TapListenerAttribute : SetAttribute> + public object VisibleAttribute : Attribute public object ColorAttribute : Attribute diff --git a/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/compose/mapControls.kt b/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/compose/mapControls.kt index e11054d..41718c4 100644 --- a/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/compose/mapControls.kt +++ b/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/compose/mapControls.kt @@ -1,5 +1,6 @@ package center.sciprog.maps.compose +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.drag import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset @@ -16,16 +17,24 @@ import kotlin.math.min * Create a modifier for Map/Scheme canvas controls on desktop * @param features a collection of features to be rendered in descending [ZAttribute] order */ +@OptIn(ExperimentalFoundationApi::class) public fun Modifier.mapControls( state: CoordinateViewScope, features: FeatureGroup, ): Modifier = with(state) { + +// //selecting all tapabales ahead of time +// val allTapable = buildMap { +// features.forEachWithAttribute(TapListenerAttribute) { _, feature, listeners -> +// put(feature, listeners) +// } +// } + pointerInput(Unit) { - fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp()) awaitPointerEventScope { while (true) { val event = awaitPointerEvent() - val coordinates = event.changes.first().position.toDpOffset().toCoordinates() + val coordinates = event.changes.first().position.toCoordinates(this) val point = space.ViewPoint(coordinates, zoom) if (event.type == PointerEventType.Move) { @@ -36,22 +45,31 @@ public fun Modifier.mapControls( } } } - if (event.type == PointerEventType.Release) { - config.onClick?.handle( - event, - point - ) - features.forEachWithAttribute(ClickListenerAttribute) { _, feature, listeners -> - if (point in feature as DomainFeature) { - listeners.forEach { it.handle(event, point) } - return@forEachWithAttribute + + if (event.type == PointerEventType.Press) { + withTimeoutOrNull(500) { + while (true) { + if (awaitPointerEvent().type == PointerEventType.Release) { + config.onClick?.handle( + event, + point + ) + features.forEachWithAttributeUntil(ClickListenerAttribute) { _, feature, listeners -> + if (point in feature as DomainFeature) { + listeners.forEach { it.handle(event, point) } + false + } else { + true + } + } + break + } } } } } } }.pointerInput(Unit) { - fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp()) awaitPointerEventScope { while (true) { val event: PointerEvent = awaitPointerEvent() @@ -84,11 +102,11 @@ public fun Modifier.mapControls( //apply drag handle and check if it prohibits the drag even propagation if (selectionStart == null) { val dragStart = space.ViewPoint( - dragChange.previousPosition.toDpOffset().toCoordinates(), + dragChange.previousPosition.toCoordinates(this), zoom ) val dragEnd = space.ViewPoint( - dragChange.position.toDpOffset().toCoordinates(), + dragChange.position.toCoordinates(this), zoom ) val dragResult = config.dragHandle?.handle(event, dragStart, dragEnd) @@ -145,4 +163,19 @@ public fun Modifier.mapControls( } } } -} \ No newline at end of file +} + +/* +.pointerInput(Unit) { + allTapable.forEach { (feature, listeners) -> + listeners.forEach { listener -> + detectTapGestures(listener.pointerMatcher, listener.keyboardFilter) { offset -> + val point = space.ViewPoint(offset.toCoordinates(this@pointerInput), zoom) + if (point in feature as DomainFeature) { + listener.onTap(point) + } + } + } + } + } + */ \ No newline at end of file