Multi-listener drag
This commit is contained in:
parent
572adf041f
commit
7e7cb0a260
@ -64,35 +64,13 @@ fun App() {
|
||||
|
||||
image(pointOne, Icons.Filled.Home)
|
||||
|
||||
var drag1 = Gmc.ofDegrees(55.744, 37.614)
|
||||
val marker1 = rectangle(55.744 to 37.614, size = DpSize(10.dp, 10.dp), color = Color.Magenta)
|
||||
val marker2 = rectangle(55.8 to 37.5, size = DpSize(10.dp, 10.dp), color = Color.Magenta)
|
||||
val marker3 = rectangle(56.0 to 37.5, size = DpSize(10.dp, 10.dp), color = Color.Magenta)
|
||||
|
||||
var drag2 = Gmc.ofDegrees(55.8, 37.5)
|
||||
|
||||
var drag3 = Gmc.ofDegrees(56.0, 37.5)
|
||||
|
||||
fun updateLine() {
|
||||
line(drag1, drag2, id = "connection1", color = Color.Magenta)
|
||||
line(drag2, drag3, id = "connection2", color = Color.Magenta)
|
||||
line(drag3, drag1, id = "connection3", color = Color.Magenta)
|
||||
}
|
||||
|
||||
rectangle(drag1, size = DpSize(10.dp, 10.dp)).draggable { _, end ->
|
||||
drag1 = end
|
||||
updateLine()
|
||||
}
|
||||
|
||||
rectangle(drag2, size = DpSize(10.dp, 10.dp)).draggable { _, end ->
|
||||
drag2 = end
|
||||
updateLine()
|
||||
}
|
||||
|
||||
rectangle(drag3, size = DpSize(10.dp, 10.dp)).draggable { _, end ->
|
||||
drag3 = end
|
||||
updateLine()
|
||||
}
|
||||
|
||||
|
||||
updateLine()
|
||||
draggableLine(marker1, marker2)
|
||||
draggableLine(marker2, marker3)
|
||||
draggableLine(marker3, marker1)
|
||||
|
||||
points(
|
||||
points = listOf(
|
||||
|
@ -8,7 +8,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import center.sciprog.maps.features.FeaturesState
|
||||
import center.sciprog.maps.features.FeatureCollection
|
||||
import center.sciprog.maps.features.ViewConfig
|
||||
import center.sciprog.maps.features.ViewPoint
|
||||
import center.sciprog.maps.features.computeBoundingBox
|
||||
@ -29,7 +29,7 @@ fun App() {
|
||||
MaterialTheme {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val schemeFeaturesState = FeaturesState.remember(XYCoordinateSpace) {
|
||||
val schemeFeaturesState = FeatureCollection.remember(XYCoordinateSpace) {
|
||||
background(1600f, 1200f) { painterResource("middle-earth.jpg") }
|
||||
circle(410.52737 to 868.7676, color = Color.Blue)
|
||||
text(410.52737 to 868.7676, "Shire", color = Color.Blue)
|
||||
|
@ -18,7 +18,7 @@ import kotlin.math.min
|
||||
public expect fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
initialViewPoint: MapViewPoint,
|
||||
featuresState: FeaturesState<Gmc>,
|
||||
featuresState: FeatureCollection<Gmc>,
|
||||
config: ViewConfig<Gmc> = ViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
@ -51,7 +51,7 @@ public fun MapView(
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
val featuresState = key(featureMap) {
|
||||
FeaturesState.build(GmcCoordinateSpace) {
|
||||
FeatureCollection.build(GmcCoordinateSpace) {
|
||||
featureMap.forEach { feature(it.key.id, it.value) }
|
||||
}
|
||||
}
|
||||
@ -80,9 +80,9 @@ public fun MapView(
|
||||
initialRectangle: Rectangle<Gmc>? = null,
|
||||
config: ViewConfig<Gmc> = ViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
buildFeatures: FeaturesState<Gmc>.() -> Unit = {},
|
||||
buildFeatures: FeatureCollection<Gmc>.() -> Unit = {},
|
||||
) {
|
||||
val featureState = FeaturesState.remember(GmcCoordinateSpace, buildFeatures)
|
||||
val featureState = FeatureCollection.remember(GmcCoordinateSpace, buildFeatures)
|
||||
|
||||
val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) {
|
||||
initialViewPoint
|
||||
|
@ -12,12 +12,12 @@ import center.sciprog.maps.features.*
|
||||
import center.sciprog.maps.features.Feature.Companion.defaultZoomRange
|
||||
|
||||
|
||||
internal fun FeaturesState<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
|
||||
internal fun FeatureCollection<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
|
||||
GeodeticMapCoordinates.ofDegrees(pair.first.toDouble(), pair.second.toDouble())
|
||||
|
||||
public typealias MapFeature = Feature<Gmc>
|
||||
|
||||
public fun FeaturesState<Gmc>.circle(
|
||||
public fun FeatureCollection<Gmc>.circle(
|
||||
centerCoordinates: Pair<Number, Number>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
size: Dp = 5.dp,
|
||||
@ -27,7 +27,7 @@ public fun FeaturesState<Gmc>.circle(
|
||||
id, CircleFeature(coordinateSpace, coordinatesOf(centerCoordinates), zoomRange, size, color)
|
||||
)
|
||||
|
||||
public fun FeaturesState<Gmc>.rectangle(
|
||||
public fun FeatureCollection<Gmc>.rectangle(
|
||||
centerCoordinates: Pair<Number, Number>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
size: DpSize = DpSize(5.dp, 5.dp),
|
||||
@ -38,7 +38,7 @@ public fun FeaturesState<Gmc>.rectangle(
|
||||
)
|
||||
|
||||
|
||||
public fun FeaturesState<Gmc>.draw(
|
||||
public fun FeatureCollection<Gmc>.draw(
|
||||
position: Pair<Number, Number>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
@ -49,7 +49,7 @@ public fun FeaturesState<Gmc>.draw(
|
||||
)
|
||||
|
||||
|
||||
public fun FeaturesState<Gmc>.line(
|
||||
public fun FeatureCollection<Gmc>.line(
|
||||
curve: GmcCurve,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
color: Color = Color.Red,
|
||||
@ -60,7 +60,7 @@ public fun FeaturesState<Gmc>.line(
|
||||
)
|
||||
|
||||
|
||||
public fun FeaturesState<Gmc>.line(
|
||||
public fun FeatureCollection<Gmc>.line(
|
||||
aCoordinates: Pair<Double, Double>,
|
||||
bCoordinates: Pair<Double, Double>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
@ -72,7 +72,7 @@ public fun FeaturesState<Gmc>.line(
|
||||
)
|
||||
|
||||
|
||||
public fun FeaturesState<Gmc>.arc(
|
||||
public fun FeatureCollection<Gmc>.arc(
|
||||
center: Pair<Double, Double>,
|
||||
radius: Distance,
|
||||
startAngle: Angle,
|
||||
@ -92,7 +92,7 @@ public fun FeaturesState<Gmc>.arc(
|
||||
)
|
||||
)
|
||||
|
||||
public fun FeaturesState<Gmc>.points(
|
||||
public fun FeatureCollection<Gmc>.points(
|
||||
points: List<Pair<Double, Double>>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
stroke: Float = 2f,
|
||||
@ -102,7 +102,7 @@ public fun FeaturesState<Gmc>.points(
|
||||
): FeatureId<PointsFeature<Gmc>> =
|
||||
feature(id, PointsFeature(coordinateSpace, points.map(::coordinatesOf), zoomRange, stroke, color, pointMode))
|
||||
|
||||
public fun FeaturesState<Gmc>.image(
|
||||
public fun FeatureCollection<Gmc>.image(
|
||||
position: Pair<Double, Double>,
|
||||
image: ImageVector,
|
||||
size: DpSize = DpSize(20.dp, 20.dp),
|
||||
@ -119,7 +119,7 @@ public fun FeaturesState<Gmc>.image(
|
||||
)
|
||||
)
|
||||
|
||||
public fun FeaturesState<Gmc>.text(
|
||||
public fun FeatureCollection<Gmc>.text(
|
||||
position: Pair<Double, Double>,
|
||||
text: String,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
|
@ -42,7 +42,7 @@ private val logger = KotlinLogging.logger("MapView")
|
||||
public actual fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
initialViewPoint: MapViewPoint,
|
||||
featuresState: FeaturesState<Gmc>,
|
||||
featuresState: FeatureCollection<Gmc>,
|
||||
config: ViewConfig<Gmc>,
|
||||
modifier: Modifier,
|
||||
): Unit = key(initialViewPoint) {
|
||||
|
@ -5,8 +5,6 @@ import androidx.compose.ui.graphics.Color
|
||||
|
||||
public object ZAttribute : Feature.Attribute<Float>
|
||||
|
||||
public object DraggableAttribute : Feature.Attribute<DragHandle<Any>>
|
||||
|
||||
public object SelectableAttribute : Feature.Attribute<(FeatureId<*>, SelectableFeature<*>) -> Unit>
|
||||
|
||||
public object VisibleAttribute : Feature.Attribute<Boolean>
|
||||
|
@ -0,0 +1,6 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
|
||||
public object DraggableAttribute : Feature.Attribute<DragHandle<Any>>
|
||||
|
||||
public object DragListenerAttribute : Feature.Attribute<Set<(begin: Any, end: Any) -> Unit>>
|
@ -49,6 +49,13 @@ public interface DraggableFeature<T : Any> : SelectableFeature<T> {
|
||||
public fun withCoordinates(newCoordinates: T): Feature<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* A draggable marker feature. Other features could be bound to this one.
|
||||
*/
|
||||
public interface MarkerFeature<T: Any>: DraggableFeature<T>{
|
||||
public val center: T
|
||||
}
|
||||
|
||||
public fun <T : Any> Iterable<Feature<T>>.computeBoundingBox(
|
||||
space: CoordinateSpace<T>,
|
||||
zoom: Float,
|
||||
@ -115,12 +122,12 @@ public class PointsFeature<T : Any>(
|
||||
|
||||
public data class CircleFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val center: T,
|
||||
override val center: T,
|
||||
override val zoomRange: FloatRange = defaultZoomRange,
|
||||
public val size: Dp = 5.dp,
|
||||
public val color: Color = Color.Red,
|
||||
override var attributes: AttributeMap = AttributeMap(),
|
||||
) : DraggableFeature<T> {
|
||||
) : MarkerFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||
space.Rectangle(center, zoom, DpSize(size, size))
|
||||
|
||||
@ -130,12 +137,12 @@ public data class CircleFeature<T : Any>(
|
||||
|
||||
public class RectangleFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val center: T,
|
||||
override val center: T,
|
||||
override val zoomRange: FloatRange = defaultZoomRange,
|
||||
public val size: DpSize = DpSize(5.dp, 5.dp),
|
||||
public val color: Color = Color.Red,
|
||||
override var attributes: AttributeMap = AttributeMap(),
|
||||
) : DraggableFeature<T> {
|
||||
) : MarkerFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||
space.Rectangle(center, zoom, size)
|
||||
|
||||
@ -193,35 +200,35 @@ public data class DrawFeature<T : Any>(
|
||||
|
||||
public data class BitmapImageFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val position: T,
|
||||
override val center: T,
|
||||
public val size: DpSize,
|
||||
public val image: ImageBitmap,
|
||||
override val zoomRange: FloatRange = defaultZoomRange,
|
||||
override var attributes: AttributeMap = AttributeMap(),
|
||||
) : DraggableFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(position, zoom, size)
|
||||
) : MarkerFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(position = newCoordinates)
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
|
||||
}
|
||||
|
||||
public data class VectorImageFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val position: T,
|
||||
override val center: T,
|
||||
public val size: DpSize,
|
||||
public val image: ImageVector,
|
||||
override val zoomRange: FloatRange = defaultZoomRange,
|
||||
override var attributes: AttributeMap = AttributeMap(),
|
||||
) : DraggableFeature<T>, PainterFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(position, zoom, size)
|
||||
) : MarkerFeature<T>, PainterFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(position = newCoordinates)
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
|
||||
|
||||
@Composable
|
||||
override fun painter(): VectorPainter = rememberVectorPainter(image)
|
||||
}
|
||||
|
||||
/**
|
||||
* A background image that is bound to coordinates and is scaled together with them
|
||||
* An image that is bound to coordinates and is scaled together with them
|
||||
*
|
||||
* @param rectangle the size of background in scheme size units. The screen units to scheme units ratio equals scale.
|
||||
*/
|
||||
|
@ -18,10 +18,23 @@ import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@JvmInline
|
||||
public value class FeatureId<out MapFeature>(public val id: String)
|
||||
public value class FeatureId<out F : Feature<*>>(public val id: String)
|
||||
|
||||
public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<T>) :
|
||||
CoordinateSpace<T> by coordinateSpace {
|
||||
public interface FeatureBuilder<T : Any> {
|
||||
|
||||
public val coordinateSpace: CoordinateSpace<T>
|
||||
|
||||
public fun <F : Feature<T>> feature(id: String?, feature: F): FeatureId<F>
|
||||
|
||||
public fun <F : Feature<T>, V> setAttribute(id: FeatureId<F>, key: Feature.Attribute<V>, value: V?)
|
||||
}
|
||||
|
||||
public fun <T : Any, F : Feature<T>> FeatureBuilder<T>.feature(id: FeatureId<F>, feature: F): FeatureId<F> =
|
||||
feature(id.id, feature)
|
||||
|
||||
public class FeatureCollection<T : Any>(
|
||||
override val coordinateSpace: CoordinateSpace<T>,
|
||||
) : CoordinateSpace<T> by coordinateSpace, FeatureBuilder<T> {
|
||||
|
||||
@PublishedApi
|
||||
internal val featureMap: MutableMap<String, Feature<T>> = mutableStateMapOf()
|
||||
@ -30,21 +43,21 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
|
||||
get() = featureMap.mapKeys { FeatureId<Feature<T>>(it.key) }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <F : Feature<T>> getFeature(id: FeatureId<F>): F = featureMap[id.id] as F
|
||||
public operator fun <F : Feature<T>> get(id: FeatureId<F>): F = featureMap[id.id] as F
|
||||
|
||||
private fun generateID(feature: Feature<T>): String = "@feature[${feature.hashCode().toUInt()}]"
|
||||
|
||||
public fun <F : Feature<T>> feature(id: String?, feature: F): FeatureId<F> {
|
||||
override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureId<F> {
|
||||
val safeId = id ?: generateID(feature)
|
||||
featureMap[safeId] = feature
|
||||
return FeatureId(safeId)
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>> feature(id: FeatureId<F>?, feature: F): FeatureId<F> = feature(id?.id, feature)
|
||||
public fun <F : Feature<T>> feature(id: FeatureId<F>, feature: F): FeatureId<F> = feature(id.id, feature)
|
||||
|
||||
|
||||
public fun <F : Feature<T>, V> setAttribute(id: FeatureId<F>, key: Feature.Attribute<V>, value: V?) {
|
||||
getFeature(id).attributes[key] = value
|
||||
override fun <F : Feature<T>, V> setAttribute(id: FeatureId<F>, key: Feature.Attribute<V>, value: V?) {
|
||||
get(id).attributes[key] = value
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,20 +67,23 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
|
||||
*
|
||||
* TODO use context receiver for that
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun FeatureId<DraggableFeature<T>>.draggable(
|
||||
constraint: ((T) -> T)? = null,
|
||||
callback: ((start: T, end: T) -> Unit) = { _, _ -> },
|
||||
callback: ((start: T, end: T) -> Unit)? = null,
|
||||
) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
if (getAttribute(this, DraggableAttribute) == null) {
|
||||
val handle = DragHandle.withPrimaryButton<Any> { _, start, end ->
|
||||
val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton DragResult(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)
|
||||
feature.attributes[DragListenerAttribute]?.forEach {
|
||||
it.invoke(startPosition, endPosition)
|
||||
}
|
||||
DragResult(ViewPoint(finalPosition, end.zoom), false)
|
||||
} else {
|
||||
DragResult(end, true)
|
||||
@ -76,6 +92,15 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
|
||||
setAttribute(this, DraggableAttribute, handle)
|
||||
}
|
||||
|
||||
//Apply callback
|
||||
if (callback != null) {
|
||||
setAttribute(
|
||||
this, DragListenerAttribute,
|
||||
((getAttribute(this, DragListenerAttribute) ?: emptySet()) + callback) as Set<(Any, Any) -> Unit>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cyclic update of a feature. Called infinitely until canceled.
|
||||
*/
|
||||
@ -84,7 +109,7 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
|
||||
update: suspend (F) -> F,
|
||||
): Job = scope.launch {
|
||||
while (isActive) {
|
||||
feature(this@updated, update(getFeature(this@updated)))
|
||||
feature(this@updated, update(get(this@updated)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,15 +123,7 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <A> getAttribute(id: FeatureId<Feature<T>>, key: Feature.Attribute<A>): A? =
|
||||
getFeature(id).attributes[key]
|
||||
|
||||
|
||||
// @Suppress("UNCHECKED_CAST")
|
||||
// public fun <T> findAllWithAttribute(key: Attribute<T>, condition: (T) -> Boolean): Set<FeatureId> {
|
||||
// return attributes.filterValues {
|
||||
// condition(it[key] as T)
|
||||
// }.keys
|
||||
// }
|
||||
get(id).attributes[key]
|
||||
|
||||
/**
|
||||
* Process all features with a given attribute from the one with highest [z] to lowest
|
||||
@ -129,8 +146,8 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
|
||||
*/
|
||||
public fun <T : Any> build(
|
||||
coordinateSpace: CoordinateSpace<T>,
|
||||
builder: FeaturesState<T>.() -> Unit = {},
|
||||
): FeaturesState<T> = FeaturesState(coordinateSpace).apply(builder)
|
||||
builder: FeatureCollection<T>.() -> Unit = {},
|
||||
): FeatureCollection<T> = FeatureCollection(coordinateSpace).apply(builder)
|
||||
|
||||
/**
|
||||
* Build and remember map feature state
|
||||
@ -138,15 +155,15 @@ public class FeaturesState<T : Any>(public val coordinateSpace: CoordinateSpace<
|
||||
@Composable
|
||||
public fun <T : Any> remember(
|
||||
coordinateSpace: CoordinateSpace<T>,
|
||||
builder: FeaturesState<T>.() -> Unit = {},
|
||||
): FeaturesState<T> = remember(builder) {
|
||||
builder: FeatureCollection<T>.() -> Unit = {},
|
||||
): FeatureCollection<T> = remember(builder) {
|
||||
build(coordinateSpace, builder)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.circle(
|
||||
public fun <T : Any> FeatureBuilder<T>.circle(
|
||||
center: T,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: Dp = 5.dp,
|
||||
@ -156,7 +173,7 @@ public fun <T : Any> FeaturesState<T>.circle(
|
||||
id, CircleFeature(coordinateSpace, center, zoomRange, size, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.rectangle(
|
||||
public fun <T : Any> FeatureBuilder<T>.rectangle(
|
||||
centerCoordinates: T,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: DpSize = DpSize(5.dp, 5.dp),
|
||||
@ -166,7 +183,7 @@ public fun <T : Any> FeaturesState<T>.rectangle(
|
||||
id, RectangleFeature(coordinateSpace, centerCoordinates, zoomRange, size, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.draw(
|
||||
public fun <T : Any> FeatureBuilder<T>.draw(
|
||||
position: T,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
@ -176,7 +193,7 @@ public fun <T : Any> FeaturesState<T>.draw(
|
||||
DrawFeature(coordinateSpace, position, zoomRange, drawFeature = draw)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.line(
|
||||
public fun <T : Any> FeatureBuilder<T>.line(
|
||||
aCoordinates: T,
|
||||
bCoordinates: T,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
@ -187,7 +204,7 @@ public fun <T : Any> FeaturesState<T>.line(
|
||||
LineFeature(coordinateSpace, aCoordinates, bCoordinates, zoomRange, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.arc(
|
||||
public fun <T : Any> FeatureBuilder<T>.arc(
|
||||
oval: Rectangle<T>,
|
||||
startAngle: Float,
|
||||
arcLength: Float,
|
||||
@ -199,7 +216,7 @@ public fun <T : Any> FeaturesState<T>.arc(
|
||||
ArcFeature(coordinateSpace, oval, startAngle, arcLength, zoomRange, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.points(
|
||||
public fun <T : Any> FeatureBuilder<T>.points(
|
||||
points: List<T>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
stroke: Float = 2f,
|
||||
@ -209,7 +226,7 @@ public fun <T : Any> FeaturesState<T>.points(
|
||||
): FeatureId<PointsFeature<T>> =
|
||||
feature(id, PointsFeature(coordinateSpace, points, zoomRange, stroke, color, pointMode))
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.image(
|
||||
public fun <T : Any> FeatureBuilder<T>.image(
|
||||
position: T,
|
||||
image: ImageVector,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
@ -227,17 +244,17 @@ public fun <T : Any> FeaturesState<T>.image(
|
||||
)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.group(
|
||||
public fun <T : Any> FeatureBuilder<T>.group(
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
builder: FeaturesState<T>.() -> Unit,
|
||||
builder: FeatureCollection<T>.() -> Unit,
|
||||
): FeatureId<FeatureGroup<T>> {
|
||||
val map = FeaturesState(coordinateSpace).apply(builder).features
|
||||
val map = FeatureCollection(coordinateSpace).apply(builder).features
|
||||
val feature = FeatureGroup(coordinateSpace, map, zoomRange)
|
||||
return feature(id, feature)
|
||||
}
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.scalableImage(
|
||||
public fun <T : Any> FeatureBuilder<T>.scalableImage(
|
||||
box: Rectangle<T>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
@ -247,7 +264,7 @@ public fun <T : Any> FeaturesState<T>.scalableImage(
|
||||
ScalableImageFeature<T>(coordinateSpace, box, zoomRange, painter = painter)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeaturesState<T>.text(
|
||||
public fun <T : Any> FeatureBuilder<T>.text(
|
||||
position: T,
|
||||
text: String,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
@ -0,0 +1,34 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
|
||||
public fun <T : Any> FeatureCollection<T>.draggableLine(
|
||||
aId: FeatureId<MarkerFeature<T>>,
|
||||
bId: FeatureId<MarkerFeature<T>>,
|
||||
zoomRange: FloatRange = Feature.defaultZoomRange,
|
||||
color: Color = Color.Red,
|
||||
id: String? = null,
|
||||
): FeatureId<LineFeature<T>> {
|
||||
var lineId: FeatureId<LineFeature<T>>? = null
|
||||
|
||||
fun drawLine(): FeatureId<LineFeature<T>> = line(
|
||||
get(aId).center,
|
||||
get(bId).center,
|
||||
zoomRange,
|
||||
color,
|
||||
lineId?.id ?: id
|
||||
).also {
|
||||
lineId = it
|
||||
}
|
||||
|
||||
aId.draggable { _, _ ->
|
||||
drawLine()
|
||||
}
|
||||
|
||||
bId.draggable { _, _ ->
|
||||
drawLine()
|
||||
}
|
||||
|
||||
return drawLine()
|
||||
}
|
@ -62,10 +62,10 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
|
||||
}
|
||||
|
||||
is BitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
|
||||
is BitmapImageFeature -> drawImage(feature.image, feature.center.toOffset())
|
||||
|
||||
is VectorImageFeature -> {
|
||||
val offset = feature.position.toOffset()
|
||||
val offset = feature.center.toOffset()
|
||||
val size = feature.size.toSize()
|
||||
translate(offset.x - size.width / 2, offset.y - size.height / 2) {
|
||||
with(painterCache[feature]!!) {
|
||||
|
@ -12,7 +12,7 @@ import center.sciprog.maps.features.*
|
||||
|
||||
internal fun Pair<Number, Number>.toCoordinates(): XY = XY(first.toFloat(), second.toFloat())
|
||||
|
||||
fun FeaturesState<XY>.background(
|
||||
fun FeatureCollection<XY>.background(
|
||||
width: Float,
|
||||
height: Float,
|
||||
offset: XY = XY(0f, 0f),
|
||||
@ -31,7 +31,7 @@ fun FeaturesState<XY>.background(
|
||||
)
|
||||
}
|
||||
|
||||
fun FeaturesState<XY>.circle(
|
||||
fun FeatureCollection<XY>.circle(
|
||||
centerCoordinates: Pair<Number, Number>,
|
||||
zoomRange: FloatRange = Feature.defaultZoomRange,
|
||||
size: Dp = 5.dp,
|
||||
@ -39,14 +39,14 @@ fun FeaturesState<XY>.circle(
|
||||
id: String? = null,
|
||||
): FeatureId<CircleFeature<XY>> = circle(centerCoordinates.toCoordinates(), zoomRange, size, color, id = id)
|
||||
|
||||
fun FeaturesState<XY>.draw(
|
||||
fun FeatureCollection<XY>.draw(
|
||||
position: Pair<Number, Number>,
|
||||
zoomRange: FloatRange = Feature.defaultZoomRange,
|
||||
id: String? = null,
|
||||
draw: DrawScope.() -> Unit,
|
||||
): FeatureId<DrawFeature<XY>> = draw(position.toCoordinates(), zoomRange = zoomRange, id = id, draw = draw)
|
||||
|
||||
fun FeaturesState<XY>.line(
|
||||
fun FeatureCollection<XY>.line(
|
||||
aCoordinates: Pair<Number, Number>,
|
||||
bCoordinates: Pair<Number, Number>,
|
||||
scaleRange: FloatRange = Feature.defaultZoomRange,
|
||||
@ -55,7 +55,7 @@ fun FeaturesState<XY>.line(
|
||||
): FeatureId<LineFeature<XY>> = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), scaleRange, color, id)
|
||||
|
||||
|
||||
public fun FeaturesState<XY>.arc(
|
||||
public fun FeatureCollection<XY>.arc(
|
||||
center: Pair<Double, Double>,
|
||||
radius: Float,
|
||||
startAngle: Float,
|
||||
@ -71,7 +71,7 @@ public fun FeaturesState<XY>.arc(
|
||||
color = color
|
||||
)
|
||||
|
||||
fun FeaturesState<XY>.image(
|
||||
fun FeatureCollection<XY>.image(
|
||||
position: Pair<Number, Number>,
|
||||
image: ImageVector,
|
||||
size: DpSize = DpSize(image.defaultWidth, image.defaultHeight),
|
||||
@ -80,7 +80,7 @@ fun FeaturesState<XY>.image(
|
||||
): FeatureId<VectorImageFeature<XY>> =
|
||||
image(position.toCoordinates(), image, size = size, zoomRange = zoomRange, id = id)
|
||||
|
||||
fun FeaturesState<XY>.text(
|
||||
fun FeatureCollection<XY>.text(
|
||||
position: Pair<Number, Number>,
|
||||
text: String,
|
||||
zoomRange: FloatRange = Feature.defaultZoomRange,
|
||||
|
@ -23,7 +23,7 @@ private val logger = KotlinLogging.logger("SchemeView")
|
||||
@Composable
|
||||
public fun SchemeView(
|
||||
initialViewPoint: ViewPoint<XY>,
|
||||
featuresState: FeaturesState<XY>,
|
||||
featuresState: FeatureCollection<XY>,
|
||||
config: ViewConfig<XY>,
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
) = key(initialViewPoint) {
|
||||
@ -96,7 +96,7 @@ public fun SchemeView(
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
val featuresState = key(featureMap) {
|
||||
FeaturesState.build(XYCoordinateSpace) {
|
||||
FeatureCollection.build(XYCoordinateSpace) {
|
||||
featureMap.forEach { feature(it.key.id, it.value) }
|
||||
}
|
||||
}
|
||||
@ -124,9 +124,9 @@ public fun SchemeView(
|
||||
initialRectangle: Rectangle<XY>? = null,
|
||||
config: ViewConfig<XY> = ViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
buildFeatures: FeaturesState<XY>.() -> Unit = {},
|
||||
buildFeatures: FeatureCollection<XY>.() -> Unit = {},
|
||||
) {
|
||||
val featureState = FeaturesState.remember(XYCoordinateSpace, buildFeatures)
|
||||
val featureState = FeatureCollection.remember(XYCoordinateSpace, buildFeatures)
|
||||
|
||||
val viewPointOverride: ViewPoint<XY> = remember(initialViewPoint, initialRectangle) {
|
||||
initialViewPoint
|
||||
|
@ -22,7 +22,7 @@ class FeatureStateSnapshot<T : Any>(
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun <T: Any> FeaturesState<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
|
||||
fun <T: Any> FeatureCollection<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
|
||||
features,
|
||||
features.values.filterIsInstance<PainterFeature<T>>().associateWith { it.painter() }
|
||||
)
|
||||
@ -83,10 +83,10 @@ fun FeatureStateSnapshot<XY>.generateSvg(
|
||||
)
|
||||
}
|
||||
|
||||
is BitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
|
||||
is BitmapImageFeature -> drawImage(feature.image, feature.center.toOffset())
|
||||
|
||||
is VectorImageFeature -> {
|
||||
val offset = feature.position.toOffset()
|
||||
val offset = feature.center.toOffset()
|
||||
val imageSize = feature.size.toSize()
|
||||
translate(offset.x - imageSize.width / 2, offset.y - imageSize.height / 2) {
|
||||
with(painterCache[feature]!!) {
|
||||
|
Loading…
Reference in New Issue
Block a user