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) line(drag3, drag1, id = "connection3", color = Color.Magenta)
} }
rectangle(drag1, size = DpSize(10.dp, 10.dp)).draggable { _, _, end -> rectangle(drag1, size = DpSize(10.dp, 10.dp)).draggable { _, end ->
drag1 = end.focus drag1 = end
updateLine() updateLine()
true
} }
rectangle(drag2, size = DpSize(10.dp, 10.dp)).draggable { _, _, end -> rectangle(drag2, size = DpSize(10.dp, 10.dp)).draggable { _, end ->
drag2 = end.focus drag2 = end
updateLine() updateLine()
true
} }
rectangle(drag3, size = DpSize(10.dp, 10.dp)).draggable { _, _, end -> rectangle(drag3, size = DpSize(10.dp, 10.dp)).draggable { _, end ->
drag3 = end.focus drag3 = end
updateLine() updateLine()
true
} }

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -87,17 +87,24 @@ public fun MapView(
val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) { val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) {
initialViewPoint initialViewPoint
?: initialRectangle?.computeViewPoint(mapTileProvider) ?: initialRectangle?.computeViewPoint(mapTileProvider)
?: featureState.features.values.computeBoundingBox(GmcCoordinateSpace,1f)?.computeViewPoint(mapTileProvider) ?: featureState.features.values.computeBoundingBox(GmcCoordinateSpace, 1f)
?.computeViewPoint(mapTileProvider)
?: MapViewPoint.globe ?: 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 -> featureState.forEachWithAttribute(DraggableAttribute) { _, handle ->
//TODO add safety @Suppress("UNCHECKED_CAST")
handle as DragHandle<Gmc> (handle as DragHandle<Gmc>)
if (!handle.handle(event, start, end)) return@withPrimaryButton false .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) drawFeature(state, painterCache, feature)
} }
} }

View File

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

View File

@ -3,7 +3,13 @@ package center.sciprog.maps.features
import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.isPrimaryPressed import androidx.compose.ui.input.pointer.isPrimaryPressed
public fun interface DragHandle<T: Any>{ /**
* @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 * @param event - qualifiers of the event used for drag
* @param start - is a point where drag begins, end is a point where drag ends * @param start - is a point where drag begins, end is a point where drag ends
@ -11,32 +17,36 @@ public fun interface DragHandle<T: Any>{
* *
* @return true if default event processors should be used after this one * @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 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 * Process only events with primary button pressed
*/ */
public fun <T: Any> withPrimaryButton( 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 -> ): DragHandle<T> = DragHandle { event, start, end ->
if (event.buttons.isPrimaryPressed) { if (event.buttons.isPrimaryPressed) {
block(event, start, end) block(event, start, end)
} else { } else {
true DragResult(end)
} }
} }
/** /**
* Combine several handles into one * Combine several handles into one
*/ */
public fun <T: Any> combine(vararg handles: DragHandle<T>): DragHandle<T> = DragHandle { event, start, end -> public fun <T : Any> combine(vararg handles: DragHandle<T>): DragHandle<T> = DragHandle { event, start, end ->
var current: ViewPoint<T> = end
handles.forEach { 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?) { 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( public fun FeatureId<DraggableFeature<T>>.draggable(
//TODO add constraints constraint: ((T) -> T)? = null,
callback: DragHandle<T> = DragHandle.bypass(), callback: ((start: T, end: T) -> Unit) = { _, _ -> },
) { ) {
val handle = DragHandle.withPrimaryButton<T> { event, start, end -> @Suppress("UNCHECKED_CAST")
val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton true val handle = DragHandle.withPrimaryButton<Any> { _, start, end ->
val boundingBox = feature.getBoundingBox(start.zoom) ?: return@withPrimaryButton true val startPosition = start.focus as T
if (start.focus in boundingBox) { val endPosition = end.focus as T
feature(id, feature.withCoordinates(end.focus)) val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton DragResult(end)
callback.handle(event, start, end) val boundingBox = feature.getBoundingBox(start.zoom) ?: return@withPrimaryButton DragResult(end)
false 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 { } else {
true DragResult(end, true)
} }
} }
setAttribute(this, DraggableAttribute, handle) setAttribute(this, DraggableAttribute, handle)
@ -98,11 +108,14 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
// }.keys // }.keys
// } // }
/**
* Process all features with a given attribute from the one with highest [z] to lowest
*/
public inline fun <A> forEachWithAttribute( public inline fun <A> forEachWithAttribute(
key: Feature.Attribute<A>, key: Feature.Attribute<A>,
block: (id: FeatureId<*>, attributeValue: A) -> Unit, block: (id: FeatureId<*>, attributeValue: A) -> Unit,
) { ) {
featureMap.forEach { (id, feature) -> featureMap.entries.sortedByDescending { it.value.z }.forEach { (id, feature) ->
feature.attributes[key]?.let { feature.attributes[key]?.let {
block(FeatureId<Feature<T>>(id), it) 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 * @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 focus: T
public val zoom: Float public val zoom: Float
} }

View File

@ -44,13 +44,13 @@ public fun <T : Any> Modifier.mapControls(
val dpEnd = dragChange.position.toDpOffset() val dpEnd = dragChange.position.toDpOffset()
//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 && !config.dragHandle.handle( if (selectionStart == null) {
val dragResult = config.dragHandle.handle(
event, event,
space.ViewPoint(dpStart.toCoordinates(), viewPoint.zoom), space.ViewPoint(dpStart.toCoordinates(), viewPoint.zoom),
space.ViewPoint(dpEnd.toCoordinates(), viewPoint.zoom) space.ViewPoint(dpEnd.toCoordinates(), viewPoint.zoom)
) )
) { if(!dragResult.handleNext) return@drag
return@drag
} }
if (event.buttons.isPrimaryPressed) { if (event.buttons.isPrimaryPressed) {

View File

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

View File

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