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.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home 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)}" "${(latitude.degrees.value).toString().take(6)}:${(longitude.degrees.value).toString().take(6)}"
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@Preview @Preview
fun App() { fun App() {
@ -120,9 +123,9 @@ fun App() {
} }
}.launchIn(scope) }.launchIn(scope)
//Add click listeners for all polygons
forEachWithType<Gmc, PolygonFeature<Gmc>> { id, feature -> forEachWithType<Gmc, PolygonFeature<Gmc>> { id, feature ->
id.onClick { id.onClick(PointerMatcher.Primary) {
println("Click on $id") println("Click on $id")
//draw in top-level scope //draw in top-level scope
with(this@MapView) { with(this@MapView) {

View File

@ -37,6 +37,7 @@ public abstract class CoordinateViewScope<T : Any>(
public abstract fun DpOffset.toCoordinates(): T public abstract fun DpOffset.toCoordinates(): T
public abstract fun T.toDpOffset(): DpOffset public abstract fun T.toDpOffset(): DpOffset
public fun T.toOffset(density: Density): Offset = with(density) { 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()) 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 Rectangle<T>.toDpRect(): DpRect
public abstract fun ViewPoint<T>.moveBy(x: Dp, y: Dp): ViewPoint<T> public abstract fun ViewPoint<T>.moveBy(x: Dp, y: Dp): ViewPoint<T>

View File

@ -1,5 +1,7 @@
package center.sciprog.maps.features package center.sciprog.maps.features
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember 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.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.PointerEvent 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.Dp
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -144,13 +147,30 @@ public data class FeatureGroup<T : Any>(
) )
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
public fun <F : DomainFeature<T>> FeatureId<F>.onClick( public fun <F : DomainFeature<T>> FeatureId<F>.onClick(
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit, onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
): FeatureId<F> = modifyAttributes { ): FeatureId<F> = modifyAttributes {
ClickListenerAttribute.add( 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> = public fun <F : Feature<T>> FeatureId<F>.color(color: Color): FeatureId<F> =
modifyAttribute(ColorAttribute, color) modifyAttribute(ColorAttribute, color)

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 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 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>( public data class ViewConfig<T : Any>(
val zoomSpeed: Float = 1f / 3f, val zoomSpeed: Float = 1f / 3f,
val onClick: MouseListener<T>? = null, 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 HoverListenerAttribute : SetAttribute<MouseListener<Any>>
//public object TapListenerAttribute : SetAttribute<TapListener<Any>>
public object VisibleAttribute : Attribute<Boolean> public object VisibleAttribute : Attribute<Boolean>
public object ColorAttribute : Attribute<Color> public object ColorAttribute : Attribute<Color>

View File

@ -1,5 +1,6 @@
package center.sciprog.maps.compose package center.sciprog.maps.compose
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.drag import androidx.compose.foundation.gestures.drag
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
@ -16,16 +17,24 @@ import kotlin.math.min
* Create a modifier for Map/Scheme canvas controls on desktop * Create a modifier for Map/Scheme canvas controls on desktop
* @param features a collection of features to be rendered in descending [ZAttribute] order * @param features a collection of features to be rendered in descending [ZAttribute] order
*/ */
@OptIn(ExperimentalFoundationApi::class)
public fun <T : Any> Modifier.mapControls( public fun <T : Any> Modifier.mapControls(
state: CoordinateViewScope<T>, state: CoordinateViewScope<T>,
features: FeatureGroup<T>, features: FeatureGroup<T>,
): Modifier = with(state) { ): Modifier = with(state) {
// //selecting all tapabales ahead of time
// val allTapable = buildMap {
// features.forEachWithAttribute(TapListenerAttribute) { _, feature, listeners ->
// put(feature, listeners)
// }
// }
pointerInput(Unit) { pointerInput(Unit) {
fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp())
awaitPointerEventScope { awaitPointerEventScope {
while (true) { while (true) {
val event = awaitPointerEvent() 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) val point = space.ViewPoint(coordinates, zoom)
if (event.type == PointerEventType.Move) { if (event.type == PointerEventType.Move) {
@ -36,22 +45,31 @@ public fun <T : Any> Modifier.mapControls(
} }
} }
} }
if (event.type == PointerEventType.Release) {
if (event.type == PointerEventType.Press) {
withTimeoutOrNull(500) {
while (true) {
if (awaitPointerEvent().type == PointerEventType.Release) {
config.onClick?.handle( config.onClick?.handle(
event, event,
point point
) )
features.forEachWithAttribute(ClickListenerAttribute) { _, feature, listeners -> features.forEachWithAttributeUntil(ClickListenerAttribute) { _, feature, listeners ->
if (point in feature as DomainFeature) { if (point in feature as DomainFeature) {
listeners.forEach { it.handle(event, point) } listeners.forEach { it.handle(event, point) }
return@forEachWithAttribute false
} else {
true
}
}
break
}
} }
} }
} }
} }
} }
}.pointerInput(Unit) { }.pointerInput(Unit) {
fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp())
awaitPointerEventScope { awaitPointerEventScope {
while (true) { while (true) {
val event: PointerEvent = awaitPointerEvent() 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 //apply drag handle and check if it prohibits the drag even propagation
if (selectionStart == null) { if (selectionStart == null) {
val dragStart = space.ViewPoint( val dragStart = space.ViewPoint(
dragChange.previousPosition.toDpOffset().toCoordinates(), dragChange.previousPosition.toCoordinates(this),
zoom zoom
) )
val dragEnd = space.ViewPoint( val dragEnd = space.ViewPoint(
dragChange.position.toDpOffset().toCoordinates(), dragChange.position.toCoordinates(this),
zoom zoom
) )
val dragResult = config.dragHandle?.handle(event, dragStart, dragEnd) 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)
}
}
}
}
}
*/