Detect taps manually

This commit is contained in:
Alexander Nozik 2023-01-06 12:36:02 +03:00
parent f288a17243
commit 5a3a4b059e
7 changed files with 121 additions and 36 deletions

View File

@ -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<Gmc, PolygonFeature<Gmc>> { id, feature ->
id.onClick {
id.onClick(PointerMatcher.Primary) {
println("Click on $id")
//draw in top-level scope
with(this@MapView) {

View File

@ -37,6 +37,7 @@ public abstract class CoordinateViewScope<T : Any>(
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<T : Any>(
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<T>.toDpRect(): DpRect
public abstract fun ViewPoint<T>.moveBy(x: Dp, y: Dp): ViewPoint<T>

View File

@ -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<T : Any>(
)
}
@Suppress("UNCHECKED_CAST")
public fun <F : DomainFeature<T>> FeatureId<F>.onClick(
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
): FeatureId<F> = modifyAttributes {
ClickListenerAttribute.add(
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
MouseListener { event, point ->
event.onClick(point as ViewPoint<T>)
}
)
}
@OptIn(ExperimentalFoundationApi::class)
@Suppress("UNCHECKED_CAST")
public fun <F : DomainFeature<T>> FeatureId<F>.onClick(
pointerMatcher: PointerMatcher,
keyboardModifiers: PointerKeyboardModifiers.() -> Boolean = {true},
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
): FeatureId<F> = modifyAttributes {
ClickListenerAttribute.add(
MouseListener { event, point ->
if (pointerMatcher.matches(event) && keyboardModifiers(event.keyboardModifiers)) {
event.onClick(point as ViewPoint<T>)
}
}
)
}
@ -163,6 +183,18 @@ public data class FeatureGroup<T : Any>(
)
}
// @Suppress("UNCHECKED_CAST")
// @OptIn(ExperimentalFoundationApi::class)
// public fun <F : DomainFeature<T>> FeatureId<F>.onTap(
// pointerMatcher: PointerMatcher = PointerMatcher.Primary,
// keyboardFilter: PointerKeyboardModifiers.() -> Boolean = { true },
// onTap: (point: ViewPoint<T>) -> Unit,
// ): FeatureId<F> = modifyAttributes {
// TapListenerAttribute.add(
// TapListener(pointerMatcher, keyboardFilter) { point -> onTap(point as ViewPoint<T>) }
// )
// }
public fun <F : Feature<T>> FeatureId<F>.color(color: Color): FeatureId<F> =
modifyAttribute(ColorAttribute, color)
@ -187,7 +219,7 @@ public data class FeatureGroup<T : Any>(
public fun <T : Any> remember(
coordinateSpace: CoordinateSpace<T>,
builder: FeatureGroup<T>.() -> Unit = {},
): FeatureGroup<T> = remember{
): FeatureGroup<T> = remember {
build(coordinateSpace, builder)
}

View File

@ -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<in T : Any> {
public fun handle(event: PointerEvent, point: ViewPoint<T>): Unit
public companion object {
public fun <T : Any> withPrimaryButton(
block: (event: PointerEvent, click: ViewPoint<T>) -> Unit,
): MouseListener<T> = MouseListener { event, click ->
if (event.buttons.isPrimaryPressed) {
block(event, click)
}
}
}
}
//@OptIn(ExperimentalFoundationApi::class)
//public class TapListener<in T : Any>(
// public val pointerMatcher: PointerMatcher,
// public val keyboardFilter: PointerKeyboardModifiers.() -> Boolean = { true },
// public val onTap: (point: ViewPoint<T>) -> Unit,
//)

View File

@ -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<in T : Any> {
public fun handle(event: PointerEvent, point: ViewPoint<T>): Unit
public companion object {
public fun <T : Any> withPrimaryButton(
block: (event: PointerEvent, click: ViewPoint<T>) -> Unit,
): MouseListener<T> = MouseListener { event, click ->
if (event.buttons.isPrimaryPressed) {
block(event, click)
}
}
}
}
public data class ViewConfig<T : Any>(
val zoomSpeed: Float = 1f / 3f,
val onClick: MouseListener<T>? = null,

View File

@ -14,6 +14,8 @@ public object ClickListenerAttribute : SetAttribute<MouseListener<Any>>
public object HoverListenerAttribute : SetAttribute<MouseListener<Any>>
//public object TapListenerAttribute : SetAttribute<TapListener<Any>>
public object VisibleAttribute : Attribute<Boolean>
public object ColorAttribute : Attribute<Color>

View File

@ -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 <T : Any> Modifier.mapControls(
state: CoordinateViewScope<T>,
features: FeatureGroup<T>,
): 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 <T : Any> 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 <T : Any> 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)
@ -146,3 +164,18 @@ public fun <T : Any> Modifier.mapControls(
}
}
}
/*
.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)
}
}
}
}
}
*/