Multi-listener drag

This commit is contained in:
Alexander Nozik 2022-12-26 17:44:01 +03:00
parent 572adf041f
commit 7e7cb0a260
14 changed files with 165 additions and 125 deletions

View File

@ -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(

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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) {

View File

@ -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>

View File

@ -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>>

View File

@ -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.
*/

View File

@ -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,26 +67,38 @@ 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")
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 {
DragResult(end, true)
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 boundingBox = feature.getBoundingBox(start.zoom) ?: return@withPrimaryButton DragResult(end)
if (startPosition in boundingBox) {
val finalPosition = constraint?.invoke(endPosition) ?: endPosition
feature(id, feature.withCoordinates(finalPosition))
feature.attributes[DragListenerAttribute]?.forEach {
it.invoke(startPosition, endPosition)
}
DragResult(ViewPoint(finalPosition, end.zoom), false)
} else {
DragResult(end, true)
}
}
setAttribute(this, DraggableAttribute, handle)
}
//Apply callback
if (callback != null) {
setAttribute(
this, DragListenerAttribute,
((getAttribute(this, DragListenerAttribute) ?: emptySet()) + callback) as Set<(Any, Any) -> Unit>
)
}
setAttribute(this, DraggableAttribute, handle)
}
/**
@ -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,

View File

@ -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()
}

View File

@ -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]!!) {

View File

@ -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,

View File

@ -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

View File

@ -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]!!) {