Cleanup drag

This commit is contained in:
Alexander Nozik 2022-12-26 11:19:08 +03:00
parent 42dbbeea58
commit 572adf041f
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
11 changed files with 82 additions and 53 deletions

View File

@ -76,22 +76,19 @@ fun App() {
line(drag3, drag1, id = "connection3", color = Color.Magenta)
}
rectangle(drag1, size = DpSize(10.dp, 10.dp)).draggable { _, _, end ->
drag1 = end.focus
rectangle(drag1, size = DpSize(10.dp, 10.dp)).draggable { _, end ->
drag1 = end
updateLine()
true
}
rectangle(drag2, size = DpSize(10.dp, 10.dp)).draggable { _, _, end ->
drag2 = end.focus
rectangle(drag2, size = DpSize(10.dp, 10.dp)).draggable { _, end ->
drag2 = end
updateLine()
true
}
rectangle(drag3, size = DpSize(10.dp, 10.dp)).draggable { _, _, end ->
drag3 = end.focus
rectangle(drag3, size = DpSize(10.dp, 10.dp)).draggable { _, end ->
drag3 = end
updateLine()
true
}

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -87,17 +87,24 @@ public fun MapView(
val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) {
initialViewPoint
?: initialRectangle?.computeViewPoint(mapTileProvider)
?: featureState.features.values.computeBoundingBox(GmcCoordinateSpace,1f)?.computeViewPoint(mapTileProvider)
?: featureState.features.values.computeBoundingBox(GmcCoordinateSpace, 1f)
?.computeViewPoint(mapTileProvider)
?: MapViewPoint.globe
}
val featureDrag: DragHandle<Gmc> = DragHandle.withPrimaryButton { event, start: ViewPoint<Gmc>, end: ViewPoint<Gmc> ->
val featureDrag: DragHandle<Gmc> = DragHandle.withPrimaryButton { event, start, end ->
featureState.forEachWithAttribute(DraggableAttribute) { _, handle ->
//TODO add safety
handle as DragHandle<Gmc>
if (!handle.handle(event, start, end)) return@withPrimaryButton false
@Suppress("UNCHECKED_CAST")
(handle as DragHandle<Gmc>)
.handle(event, start, end)
.takeIf { !it.handleNext }
?.let {
//we expect it already have no bypass
return@withPrimaryButton it
}
true
}
//bypass
DragResult(end)
}

View File

@ -127,7 +127,7 @@ public actual fun MapView(
)
}
featuresState.features.values.filter { viewPoint.zoom in it.zoomRange }.sortedBy { it.depth }.forEach { feature ->
featuresState.features.values.filter { viewPoint.zoom in it.zoomRange }.sortedBy { it.z }.forEach { feature ->
drawFeature(state, painterCache, feature)
}
}

View File

@ -3,9 +3,9 @@ package center.sciprog.maps.features
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.ui.graphics.Color
public object DepthAttribute : Feature.Attribute<Float>
public object ZAttribute : Feature.Attribute<Float>
public object DraggableAttribute : Feature.Attribute<DragHandle<*>>
public object DraggableAttribute : Feature.Attribute<DragHandle<Any>>
public object SelectableAttribute : Feature.Attribute<(FeatureId<*>, SelectableFeature<*>) -> Unit>
@ -46,8 +46,8 @@ public class AttributeMap {
override fun toString(): String = "AttributeMap(value=${map.entries})"
}
public var Feature<*>.depth: Float
get() = attributes[DepthAttribute] ?: 0f
public var Feature<*>.z: Float
get() = attributes[ZAttribute] ?: 0f
set(value) {
attributes[DepthAttribute] = value
attributes[ZAttribute] = value
}

View File

@ -3,6 +3,12 @@ package center.sciprog.maps.features
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.isPrimaryPressed
/**
* @param result - the endpoint of the drag to perform constrained drag
* @param handleNext - if false do not evaluate subsequent drag handles
*/
public data class DragResult<T : Any>(val result: ViewPoint<T>, val handleNext: Boolean = true)
public fun interface DragHandle<T : Any> {
/**
* @param event - qualifiers of the event used for drag
@ -11,21 +17,21 @@ public fun interface DragHandle<T: Any>{
*
* @return true if default event processors should be used after this one
*/
public fun handle(event: PointerEvent, start: ViewPoint<T>, end: ViewPoint<T>): Boolean
public fun handle(event: PointerEvent, start: ViewPoint<T>, end: ViewPoint<T>): DragResult<T>
public companion object {
public fun <T: Any> bypass(): DragHandle<T> = DragHandle<T> { _, _, _ -> true }
public fun <T : Any> bypass(): DragHandle<T> = DragHandle<T> { _, _, end -> DragResult(end) }
/**
* Process only events with primary button pressed
*/
public fun <T : Any> withPrimaryButton(
block: (event: PointerEvent, start: ViewPoint<T>, end: ViewPoint<T>) -> Boolean,
block: (event: PointerEvent, start: ViewPoint<T>, end: ViewPoint<T>) -> DragResult<T>,
): DragHandle<T> = DragHandle { event, start, end ->
if (event.buttons.isPrimaryPressed) {
block(event, start, end)
} else {
true
DragResult(end)
}
}
@ -33,10 +39,14 @@ public fun interface DragHandle<T: Any>{
* Combine several handles into one
*/
public fun <T : Any> combine(vararg handles: DragHandle<T>): DragHandle<T> = DragHandle { event, start, end ->
var current: ViewPoint<T> = end
handles.forEach {
if (!it.handle(event, start, end)) return@DragHandle false
val result = it.handle(event, start, current)
if (!result.handleNext) return@DragHandle result else {
current = result.result
}
return@DragHandle true
}
return@DragHandle DragResult(current)
}
}
}

View File

@ -44,23 +44,33 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
public fun <F : Feature<T>, V> setAttribute(id: FeatureId<F>, key: Feature.Attribute<V>, value: V?) {
getFeature(id).attributes.set(key, value)
getFeature(id).attributes[key] = value
}
//TODO use context receiver for that
/**
* Add drag to this feature
*
* @param constraint optional drag constraint
*
* TODO use context receiver for that
*/
public fun FeatureId<DraggableFeature<T>>.draggable(
//TODO add constraints
callback: DragHandle<T> = DragHandle.bypass(),
constraint: ((T) -> T)? = null,
callback: ((start: T, end: T) -> Unit) = { _, _ -> },
) {
val handle = DragHandle.withPrimaryButton<T> { event, start, end ->
val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton true
val boundingBox = feature.getBoundingBox(start.zoom) ?: return@withPrimaryButton true
if (start.focus in boundingBox) {
feature(id, feature.withCoordinates(end.focus))
callback.handle(event, start, end)
false
@Suppress("UNCHECKED_CAST")
val handle = DragHandle.withPrimaryButton<Any> { _, start, end ->
val startPosition = start.focus as T
val endPosition = end.focus as T
val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton DragResult(end)
val boundingBox = feature.getBoundingBox(start.zoom) ?: return@withPrimaryButton DragResult(end)
if (startPosition in boundingBox) {
val finalPosition = constraint?.invoke(endPosition) ?: endPosition
feature(id, feature.withCoordinates(finalPosition))
callback(startPosition, finalPosition)
DragResult(ViewPoint(finalPosition, end.zoom), false)
} else {
true
DragResult(end, true)
}
}
setAttribute(this, DraggableAttribute, handle)
@ -98,11 +108,14 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
// }.keys
// }
/**
* Process all features with a given attribute from the one with highest [z] to lowest
*/
public inline fun <A> forEachWithAttribute(
key: Feature.Attribute<A>,
block: (id: FeatureId<*>, attributeValue: A) -> Unit,
) {
featureMap.forEach { (id, feature) ->
featureMap.entries.sortedByDescending { it.value.z }.forEach { (id, feature) ->
feature.attributes[key]?.let {
block(FeatureId<Feature<T>>(id), it)
}

View File

@ -3,7 +3,7 @@ package center.sciprog.maps.features
/**
* @param T type of coordinates used for the view point
*/
public interface ViewPoint<T: Any> {
public interface ViewPoint<out T: Any> {
public val focus: T
public val zoom: Float
}

View File

@ -44,13 +44,13 @@ public fun <T : Any> Modifier.mapControls(
val dpEnd = dragChange.position.toDpOffset()
//apply drag handle and check if it prohibits the drag even propagation
if (selectionStart == null && !config.dragHandle.handle(
if (selectionStart == null) {
val dragResult = config.dragHandle.handle(
event,
space.ViewPoint(dpStart.toCoordinates(), viewPoint.zoom),
space.ViewPoint(dpEnd.toCoordinates(), viewPoint.zoom)
)
) {
return@drag
if(!dragResult.handleNext) return@drag
}
if (event.buttons.isPrimaryPressed) {

View File

@ -26,7 +26,7 @@ fun FeaturesState<XY>.background(
return feature(
id,
ScalableImageFeature(coordinateSpace, box, painter = painter).apply {
depth = -100f
z = -100f
}
)
}

View File

@ -50,7 +50,7 @@ public fun SchemeView(
clipRect {
featuresState.features.values
.filter { viewPoint.zoom in it.zoomRange }
.sortedBy { it.depth }
.sortedBy { it.z }
.forEach { background ->
drawFeature(state, painterCache, background)
}
@ -139,10 +139,12 @@ public fun SchemeView(
DragHandle.withPrimaryButton { event, start: ViewPoint<XY>, end: ViewPoint<XY> ->
featureState.forEachWithAttribute(DraggableAttribute) { _, handle ->
//TODO add safety
handle as DragHandle<XY>
if (!handle.handle(event, start, end)) return@withPrimaryButton false
(handle as DragHandle<XY>)
.handle(event, start, end)
.takeIf { !it.handleNext }
?.let { return@withPrimaryButton it }
}
true
DragResult(end)
}