Detect taps manually
This commit is contained in:
parent
f288a17243
commit
5a3a4b059e
@ -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) {
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
//)
|
@ -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,
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
Loading…
Reference in New Issue
Block a user