Change the working of groups
This commit is contained in:
parent
26c3e589da
commit
a8a3da7e70
@ -22,6 +22,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.PI
|
||||
@ -64,13 +66,13 @@ fun App() {
|
||||
|
||||
image(pointOne, Icons.Filled.Home)
|
||||
|
||||
val marker1 = rectangle(55.744 to 38.614, size = DpSize(10.dp, 10.dp), color = Color.Magenta)
|
||||
val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp), color = Color.Magenta)
|
||||
val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp), color = Color.Magenta)
|
||||
val marker1 = rectangle(55.744 to 38.614, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
|
||||
val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
|
||||
val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
|
||||
|
||||
draggableLine(marker1, marker2, color = Color.Blue)
|
||||
draggableLine(marker2, marker3, color = Color.Blue)
|
||||
draggableLine(marker3, marker1, color = Color.Blue)
|
||||
draggableLine(marker1, marker2).color(Color.Blue)
|
||||
draggableLine(marker2, marker3).color(Color.Blue)
|
||||
draggableLine(marker3, marker1).color(Color.Blue)
|
||||
|
||||
points(
|
||||
points = listOf(
|
||||
@ -85,13 +87,16 @@ fun App() {
|
||||
)
|
||||
|
||||
//remember feature ID
|
||||
circle(
|
||||
val circleId = circle(
|
||||
centerCoordinates = pointTwo,
|
||||
).updated(scope) {
|
||||
)
|
||||
scope.launch {
|
||||
while (isActive) {
|
||||
delay(200)
|
||||
//Overwrite a feature with new color
|
||||
it.copy(color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()))
|
||||
circleId.color(Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// draw(position = pointThree) {
|
||||
// drawLine(start = Offset(-10f, -10f), end = Offset(10f, 10f), color = Color.Red)
|
||||
@ -105,16 +110,20 @@ fun App() {
|
||||
|
||||
centerCoordinates.filterNotNull().onEach {
|
||||
group(id = "center") {
|
||||
circle(center = it, color = Color.Blue, id = "circle", size = 1.dp)
|
||||
text(position = it, it.toShortString(), id = "text", color = Color.Blue)
|
||||
circle(center = it, id = "circle", size = 1.dp).color(Color.Blue)
|
||||
text(position = it, it.toShortString(), id = "text").color(Color.Blue)
|
||||
}
|
||||
}.launchIn(scope)
|
||||
|
||||
features.forEach { (id, feature) ->
|
||||
visit { id, feature ->
|
||||
if (feature is PolygonFeature) {
|
||||
(id as FeatureId<PolygonFeature<Gmc>>).onHover {
|
||||
println("Hover on $id")
|
||||
points(feature.points, color = Color.Blue, id = "selected", attributes = Attributes(ZAttribute, 10f))
|
||||
points(
|
||||
feature.points,
|
||||
id = "selected",
|
||||
attributes = Attributes(ZAttribute, 10f)
|
||||
).color(Color.Blue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.FeatureCollection
|
||||
import center.sciprog.maps.features.FeatureGroup
|
||||
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 = FeatureCollection.remember(XYCoordinateSpace) {
|
||||
val schemeFeaturesState = FeatureGroup.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)
|
||||
@ -53,7 +53,7 @@ fun App() {
|
||||
}
|
||||
|
||||
val initialViewPoint: ViewPoint<XY> = remember {
|
||||
schemeFeaturesState.features.values.computeBoundingBox(XYCoordinateSpace, 1f)?.computeViewPoint()
|
||||
schemeFeaturesState.features.computeBoundingBox(XYCoordinateSpace, 1f)?.computeViewPoint()
|
||||
?: XYViewPoint(XY(0f, 0f))
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import center.sciprog.maps.features.*
|
||||
@Composable
|
||||
public expect fun MapView(
|
||||
mapState: MapViewScope,
|
||||
featuresState: FeatureCollection<Gmc>,
|
||||
featuresState: FeatureGroup<Gmc>,
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
@ -29,7 +29,7 @@ public fun MapView(
|
||||
) {
|
||||
|
||||
val featureState = remember(featureMap) {
|
||||
FeatureCollection.build(WebMercatorSpace) {
|
||||
FeatureGroup.build(WebMercatorSpace) {
|
||||
featureMap.forEach { feature(it.key.id, it.value) }
|
||||
}
|
||||
}
|
||||
@ -38,7 +38,7 @@ public fun MapView(
|
||||
mapTileProvider,
|
||||
config,
|
||||
initialViewPoint = initialViewPoint,
|
||||
initialRectangle = initialRectangle ?: featureState.features.values.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE),
|
||||
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE),
|
||||
)
|
||||
|
||||
MapView(mapState, featureState, modifier)
|
||||
@ -58,16 +58,16 @@ public fun MapView(
|
||||
initialRectangle: Rectangle<Gmc>? = null,
|
||||
config: ViewConfig<Gmc> = ViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
buildFeatures: FeatureCollection<Gmc>.() -> Unit = {},
|
||||
buildFeatures: FeatureGroup<Gmc>.() -> Unit = {},
|
||||
) {
|
||||
|
||||
val featureState = FeatureCollection.remember(WebMercatorSpace, buildFeatures)
|
||||
val featureState = FeatureGroup.remember(WebMercatorSpace, buildFeatures)
|
||||
|
||||
val mapState: MapViewScope = rememberMapState(
|
||||
mapTileProvider,
|
||||
config,
|
||||
initialViewPoint = initialViewPoint,
|
||||
initialRectangle = initialRectangle ?: featureState.features.values.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE),
|
||||
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE),
|
||||
)
|
||||
|
||||
MapView(mapState, featureState, modifier)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package center.sciprog.maps.compose
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PointMode
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
@ -11,73 +10,62 @@ import center.sciprog.maps.coordinates.*
|
||||
import center.sciprog.maps.features.*
|
||||
|
||||
|
||||
internal fun FeatureBuilder<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
|
||||
internal fun FeatureGroup<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
|
||||
GeodeticMapCoordinates.ofDegrees(pair.first.toDouble(), pair.second.toDouble())
|
||||
|
||||
public typealias MapFeature = Feature<Gmc>
|
||||
|
||||
public fun FeatureBuilder<Gmc>.circle(
|
||||
public fun FeatureGroup<Gmc>.circle(
|
||||
centerCoordinates: Pair<Number, Number>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: Dp = 5.dp,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<CircleFeature<Gmc>> = feature(
|
||||
id, CircleFeature(space, coordinatesOf(centerCoordinates), zoomRange, size, color)
|
||||
id, CircleFeature(space, coordinatesOf(centerCoordinates), size)
|
||||
)
|
||||
|
||||
public fun FeatureBuilder<Gmc>.rectangle(
|
||||
public fun FeatureGroup<Gmc>.rectangle(
|
||||
centerCoordinates: Pair<Number, Number>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: DpSize = DpSize(5.dp, 5.dp),
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<RectangleFeature<Gmc>> = feature(
|
||||
id, RectangleFeature(space, coordinatesOf(centerCoordinates), zoomRange, size, color)
|
||||
id, RectangleFeature(space, coordinatesOf(centerCoordinates), size)
|
||||
)
|
||||
|
||||
|
||||
public fun FeatureBuilder<Gmc>.draw(
|
||||
public fun FeatureGroup<Gmc>.draw(
|
||||
position: Pair<Number, Number>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
draw: DrawScope.() -> Unit,
|
||||
): FeatureId<DrawFeature<Gmc>> = feature(
|
||||
id,
|
||||
DrawFeature(space, coordinatesOf(position), zoomRange, drawFeature = draw)
|
||||
DrawFeature(space, coordinatesOf(position), drawFeature = draw)
|
||||
)
|
||||
|
||||
|
||||
public fun FeatureBuilder<Gmc>.line(
|
||||
public fun FeatureGroup<Gmc>.line(
|
||||
curve: GmcCurve,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<LineFeature<Gmc>> = feature(
|
||||
id,
|
||||
LineFeature(space, curve.forward.coordinates, curve.backward.coordinates, zoomRange, color)
|
||||
LineFeature(space, curve.forward.coordinates, curve.backward.coordinates)
|
||||
)
|
||||
|
||||
|
||||
public fun FeatureBuilder<Gmc>.line(
|
||||
public fun FeatureGroup<Gmc>.line(
|
||||
aCoordinates: Pair<Double, Double>,
|
||||
bCoordinates: Pair<Double, Double>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<LineFeature<Gmc>> = feature(
|
||||
id,
|
||||
LineFeature(space, coordinatesOf(aCoordinates), coordinatesOf(bCoordinates), zoomRange, color)
|
||||
LineFeature(space, coordinatesOf(aCoordinates), coordinatesOf(bCoordinates))
|
||||
)
|
||||
|
||||
|
||||
public fun FeatureBuilder<Gmc>.arc(
|
||||
public fun FeatureGroup<Gmc>.arc(
|
||||
center: Pair<Double, Double>,
|
||||
radius: Distance,
|
||||
startAngle: Angle,
|
||||
arcLength: Angle,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<ArcFeature<Gmc>> = feature(
|
||||
id,
|
||||
@ -86,26 +74,21 @@ public fun FeatureBuilder<Gmc>.arc(
|
||||
oval = space.Rectangle(coordinatesOf(center), radius, radius),
|
||||
startAngle = startAngle.radians.toFloat(),
|
||||
arcLength = arcLength.radians.toFloat(),
|
||||
zoomRange = zoomRange,
|
||||
color = color
|
||||
)
|
||||
)
|
||||
|
||||
public fun FeatureBuilder<Gmc>.points(
|
||||
public fun FeatureGroup<Gmc>.points(
|
||||
points: List<Pair<Double, Double>>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
stroke: Float = 2f,
|
||||
color: Color = defaultColor,
|
||||
pointMode: PointMode = PointMode.Points,
|
||||
id: String? = null,
|
||||
): FeatureId<PointsFeature<Gmc>> =
|
||||
feature(id, PointsFeature(space, points.map(::coordinatesOf), zoomRange, stroke, color, pointMode))
|
||||
feature(id, PointsFeature(space, points.map(::coordinatesOf), stroke, pointMode))
|
||||
|
||||
public fun FeatureBuilder<Gmc>.image(
|
||||
public fun FeatureGroup<Gmc>.image(
|
||||
position: Pair<Double, Double>,
|
||||
image: ImageVector,
|
||||
size: DpSize = DpSize(20.dp, 20.dp),
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
): FeatureId<VectorImageFeature<Gmc>> = feature(
|
||||
id,
|
||||
@ -114,18 +97,15 @@ public fun FeatureBuilder<Gmc>.image(
|
||||
coordinatesOf(position),
|
||||
size,
|
||||
image,
|
||||
zoomRange
|
||||
)
|
||||
)
|
||||
|
||||
public fun FeatureBuilder<Gmc>.text(
|
||||
public fun FeatureGroup<Gmc>.text(
|
||||
position: Pair<Double, Double>,
|
||||
text: String,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
font: FeatureFont.() -> Unit = { size = 16f },
|
||||
id: String? = null,
|
||||
): FeatureId<TextFeature<Gmc>> = feature(
|
||||
id,
|
||||
TextFeature(space, coordinatesOf(position), text, zoomRange, color, fontConfig = font)
|
||||
TextFeature(space, coordinatesOf(position), text, fontConfig = font)
|
||||
)
|
||||
|
@ -15,10 +15,10 @@ import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import center.sciprog.maps.coordinates.Gmc
|
||||
import center.sciprog.maps.features.FeatureCollection
|
||||
import center.sciprog.maps.features.FeatureGroup
|
||||
import center.sciprog.maps.features.PainterFeature
|
||||
import center.sciprog.maps.features.drawFeature
|
||||
import center.sciprog.maps.features.z
|
||||
import center.sciprog.maps.features.zoomRange
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import mu.KotlinLogging
|
||||
@ -44,7 +44,7 @@ private val logger = KotlinLogging.logger("MapView")
|
||||
@Composable
|
||||
public actual fun MapView(
|
||||
mapState: MapViewScope,
|
||||
featuresState: FeatureCollection<Gmc>,
|
||||
featuresState: FeatureGroup<Gmc>,
|
||||
modifier: Modifier,
|
||||
): Unit = with(mapState) {
|
||||
|
||||
@ -90,9 +90,10 @@ public actual fun MapView(
|
||||
}
|
||||
|
||||
val painterCache: Map<PainterFeature<Gmc>, Painter> = key(featuresState) {
|
||||
featuresState.features.values.filterIsInstance<PainterFeature<Gmc>>().associateWith { it.getPainter() }
|
||||
featuresState.features.filterIsInstance<PainterFeature<Gmc>>().associateWith { it.getPainter() }
|
||||
}
|
||||
|
||||
|
||||
Canvas(modifier = modifier.mapControls(mapState, featuresState.features).fillMaxSize()) {
|
||||
|
||||
if (canvasSize != size.toDpSize()) {
|
||||
@ -119,7 +120,7 @@ public actual fun MapView(
|
||||
)
|
||||
}
|
||||
|
||||
featuresState.features.values.filter { viewPoint.zoom in it.zoomRange }.sortedBy { it.z }
|
||||
featuresState.features.filter { viewPoint.zoom in it.zoomRange }
|
||||
.forEach { feature ->
|
||||
drawFeature(mapState, painterCache, feature)
|
||||
}
|
||||
|
@ -18,4 +18,6 @@ public object VisibleAttribute : Attribute<Boolean>
|
||||
|
||||
public object ColorAttribute : Attribute<Color>
|
||||
|
||||
public object ZoomRangeAttribute : Attribute<FloatRange>
|
||||
|
||||
public object AlphaAttribute : Attribute<Float>
|
||||
|
@ -21,8 +21,6 @@ public interface Feature<T : Any> {
|
||||
|
||||
public val space: CoordinateSpace<T>
|
||||
|
||||
public val zoomRange: FloatRange
|
||||
|
||||
public val attributes: Attributes
|
||||
|
||||
public fun getBoundingBox(zoom: Float): Rectangle<T>?
|
||||
@ -30,6 +28,11 @@ public interface Feature<T : Any> {
|
||||
public fun withAttributes(modify: Attributes.() -> Attributes): Feature<T>
|
||||
}
|
||||
|
||||
public val Feature<*>.color: Color? get() = attributes[ColorAttribute]
|
||||
|
||||
public val Feature<*>.zoomRange: FloatRange
|
||||
get() = attributes[ZoomRangeAttribute] ?: Float.NEGATIVE_INFINITY..Float.POSITIVE_INFINITY
|
||||
|
||||
public interface PainterFeature<T : Any> : Feature<T> {
|
||||
@Composable
|
||||
public fun getPainter(): Painter
|
||||
@ -65,7 +68,6 @@ public fun <T : Any> Iterable<Feature<T>>.computeBoundingBox(
|
||||
@Stable
|
||||
public data class FeatureSelector<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
public val selector: (zoom: Float) -> Feature<T>,
|
||||
) : Feature<T> {
|
||||
@ -81,7 +83,6 @@ public data class PathFeature<T : Any>(
|
||||
public val rectangle: Rectangle<T>,
|
||||
public val path: Path,
|
||||
public val brush: Brush,
|
||||
override val zoomRange: FloatRange,
|
||||
public val style: DrawStyle = Fill,
|
||||
public val targetRect: Rect = path.getBounds(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
@ -94,7 +95,6 @@ public data class PathFeature<T : Any>(
|
||||
brush = brush,
|
||||
style = style,
|
||||
targetRect = targetRect,
|
||||
zoomRange = zoomRange
|
||||
)
|
||||
}
|
||||
|
||||
@ -106,9 +106,7 @@ public data class PathFeature<T : Any>(
|
||||
public data class PointsFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val points: List<T>,
|
||||
override val zoomRange: FloatRange,
|
||||
public val stroke: Float = 2f,
|
||||
public val color: Color = Color.Red,
|
||||
public val pointMode: PointMode = PointMode.Points,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : Feature<T> {
|
||||
@ -125,8 +123,6 @@ public data class PointsFeature<T : Any>(
|
||||
public data class PolygonFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val points: List<T>,
|
||||
override val zoomRange: FloatRange,
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : DomainFeature<T> {
|
||||
|
||||
@ -136,7 +132,8 @@ public data class PolygonFeature<T : Any>(
|
||||
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T>? = boundingBox
|
||||
|
||||
override fun contains(viewPoint: ViewPoint<T>): Boolean = viewPoint.focus in boundingBox!!//with(space) { viewPoint.focus.isInsidePolygon(points) }
|
||||
override fun contains(viewPoint: ViewPoint<T>): Boolean =
|
||||
viewPoint.focus in boundingBox!!//with(space) { viewPoint.focus.isInsidePolygon(points) }
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
@ -145,9 +142,7 @@ public data class PolygonFeature<T : Any>(
|
||||
public data class CircleFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
override val center: T,
|
||||
override val zoomRange: FloatRange,
|
||||
public val size: Dp = 5.dp,
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : MarkerFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||
@ -162,9 +157,7 @@ public data class CircleFeature<T : Any>(
|
||||
public data class RectangleFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
override val center: T,
|
||||
override val zoomRange: FloatRange,
|
||||
public val size: DpSize = DpSize(5.dp, 5.dp),
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : MarkerFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||
@ -180,8 +173,6 @@ public data class LineFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val a: T,
|
||||
public val b: T,
|
||||
override val zoomRange: FloatRange,
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : DomainFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||
@ -208,8 +199,6 @@ public data class ArcFeature<T : Any>(
|
||||
public val oval: Rectangle<T>,
|
||||
public val startAngle: Float,
|
||||
public val arcLength: Float,
|
||||
override val zoomRange: FloatRange,
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : DraggableFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = oval
|
||||
@ -223,7 +212,6 @@ public data class ArcFeature<T : Any>(
|
||||
public data class DrawFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val position: T,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
public val drawFeature: DrawScope.() -> Unit,
|
||||
) : DraggableFeature<T> {
|
||||
@ -240,7 +228,6 @@ public data class BitmapImageFeature<T : Any>(
|
||||
override val center: T,
|
||||
public val size: DpSize,
|
||||
public val image: ImageBitmap,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : MarkerFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
|
||||
@ -256,7 +243,6 @@ public data class VectorImageFeature<T : Any>(
|
||||
override val center: T,
|
||||
public val size: DpSize,
|
||||
public val image: ImageVector,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : MarkerFeature<T>, PainterFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
|
||||
@ -277,7 +263,6 @@ public data class VectorImageFeature<T : Any>(
|
||||
public data class ScalableImageFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val rectangle: Rectangle<T>,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
public val painter: @Composable () -> Painter,
|
||||
) : Feature<T>, PainterFeature<T> {
|
||||
@ -290,28 +275,10 @@ public data class ScalableImageFeature<T : Any>(
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A group of other features
|
||||
*/
|
||||
public data class FeatureGroup<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val children: Map<FeatureId<*>, Feature<T>>,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : Feature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
|
||||
children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
|
||||
}
|
||||
|
||||
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = attributes)
|
||||
}
|
||||
|
||||
public data class TextFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val position: T,
|
||||
public val text: String,
|
||||
override val zoomRange: FloatRange,
|
||||
public val color: Color = Color.Black,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
public val fontConfig: FeatureFont.() -> Unit,
|
||||
) : DraggableFeature<T> {
|
||||
|
@ -1,335 +0,0 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PointMode
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
@JvmInline
|
||||
public value class FeatureId<out F : Feature<*>>(public val id: String)
|
||||
|
||||
public interface FeatureBuilder<T : Any> {
|
||||
|
||||
public val space: CoordinateSpace<T>
|
||||
|
||||
public fun generateUID(feature: Feature<T>?): String
|
||||
|
||||
public fun <F : Feature<T>> feature(id: String?, feature: F): FeatureId<F>
|
||||
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.withAttribute(key: Attribute<V>, value: V?): FeatureId<F>
|
||||
|
||||
public val defaultColor: Color get() = Color.Red
|
||||
|
||||
public val defaultZoomRange: FloatRange get() = 0f..Float.POSITIVE_INFINITY
|
||||
}
|
||||
|
||||
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 space: CoordinateSpace<T>,
|
||||
) : CoordinateSpace<T> by space, FeatureBuilder<T> {
|
||||
|
||||
@PublishedApi
|
||||
internal val featureMap: SnapshotStateMap<String, Feature<T>> = mutableStateMapOf()
|
||||
|
||||
public val features: Map<FeatureId<*>, Feature<T>>
|
||||
get() = featureMap.mapKeys { FeatureId<Feature<T>>(it.key) }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public operator fun <F : Feature<T>> get(id: FeatureId<F>): F =
|
||||
featureMap[id.id]?.let { it as F } ?: error("Feature with id=$id not found")
|
||||
|
||||
private var uidCounter = 0
|
||||
|
||||
override fun generateUID(feature: Feature<T>?): String = if(feature == null){
|
||||
"@group[${uidCounter++}]"
|
||||
} else {
|
||||
"@${feature::class.simpleName}[${uidCounter++}]"
|
||||
}
|
||||
|
||||
override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureId<F> {
|
||||
val safeId = id ?: generateUID(feature)
|
||||
featureMap[safeId] = feature
|
||||
return FeatureId(safeId)
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>> feature(id: FeatureId<F>, feature: F): FeatureId<F> = feature(id.id, feature)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <A> getAttribute(id: FeatureId<Feature<T>>, key: Attribute<A>): A? =
|
||||
get(id).attributes[key]
|
||||
|
||||
/**
|
||||
* Process all features with a given attribute from the one with highest [z] to lowest
|
||||
*/
|
||||
public inline fun <A> forEachWithAttribute(
|
||||
key: Attribute<A>,
|
||||
block: (id: FeatureId<*>, feature: Feature<T>, attributeValue: A) -> Unit,
|
||||
) {
|
||||
featureMap.entries.sortedByDescending { it.value.z }.forEach { (id, feature) ->
|
||||
feature.attributes[key]?.let {
|
||||
block(FeatureId<Feature<T>>(id), feature, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.modifyAttributes(modify: Attributes.() -> Attributes) {
|
||||
feature(this, get(this).withAttributes(modify))
|
||||
}
|
||||
|
||||
override fun <F : Feature<T>, V> FeatureId<F>.withAttribute(key: Attribute<V>, value: V?): FeatureId<F> {
|
||||
feature(this, get(this).withAttributes { withAttribute(key, value) })
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add drag to this feature
|
||||
*
|
||||
* @param constraint optional drag constraint
|
||||
*
|
||||
* TODO use context receiver for that
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun FeatureId<DraggableFeature<T>>.draggable(
|
||||
constraint: ((T) -> T)? = null,
|
||||
listener: (PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit)? = null,
|
||||
) {
|
||||
if (getAttribute(this, DraggableAttribute) == null) {
|
||||
val handle = DragHandle.withPrimaryButton<Any> { event, start, end ->
|
||||
val feature = featureMap[id] as? DraggableFeature<T> ?: return@withPrimaryButton DragResult(end)
|
||||
start as ViewPoint<T>
|
||||
end as ViewPoint<T>
|
||||
if (start in feature) {
|
||||
val finalPosition = constraint?.invoke(end.focus) ?: end.focus
|
||||
feature(id, feature.withCoordinates(finalPosition))
|
||||
feature.attributes[DragListenerAttribute]?.forEach {
|
||||
it.handle(event, start, ViewPoint(finalPosition, end.zoom))
|
||||
}
|
||||
DragResult(ViewPoint(finalPosition, end.zoom), false)
|
||||
} else {
|
||||
DragResult(end, true)
|
||||
}
|
||||
}
|
||||
this.withAttribute(DraggableAttribute, handle)
|
||||
}
|
||||
|
||||
//Apply callback
|
||||
if (listener != null) {
|
||||
onDrag(listener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun FeatureId<DraggableFeature<T>>.onDrag(
|
||||
listener: PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
withAttribute(
|
||||
DragListenerAttribute,
|
||||
(getAttribute(this, DragListenerAttribute) ?: emptySet()) +
|
||||
DragListener { event, from, to ->
|
||||
event.listener(from as ViewPoint<T>, to as ViewPoint<T>)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <F : DomainFeature<T>> FeatureId<F>.onClick(
|
||||
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
withAttribute(
|
||||
ClickListenerAttribute,
|
||||
(getAttribute(this, ClickListenerAttribute) ?: emptySet()) +
|
||||
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <F : DomainFeature<T>> FeatureId<F>.onHover(
|
||||
onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
withAttribute(
|
||||
HoverListenerAttribute,
|
||||
(getAttribute(this, HoverListenerAttribute) ?: emptySet()) +
|
||||
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cyclic update of a feature. Called infinitely until canceled.
|
||||
*/
|
||||
public fun <F : Feature<T>> FeatureId<F>.updated(
|
||||
scope: CoroutineScope,
|
||||
update: suspend (F) -> F,
|
||||
): Job = scope.launch {
|
||||
while (isActive) {
|
||||
feature(this@updated, update(get(this@updated)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public companion object {
|
||||
|
||||
/**
|
||||
* Build, but do not remember map feature state
|
||||
*/
|
||||
public fun <T : Any> build(
|
||||
coordinateSpace: CoordinateSpace<T>,
|
||||
builder: FeatureCollection<T>.() -> Unit = {},
|
||||
): FeatureCollection<T> = FeatureCollection(coordinateSpace).apply(builder)
|
||||
|
||||
/**
|
||||
* Build and remember map feature state
|
||||
*/
|
||||
@Composable
|
||||
public fun <T : Any> remember(
|
||||
coordinateSpace: CoordinateSpace<T>,
|
||||
builder: FeatureCollection<T>.() -> Unit = {},
|
||||
): FeatureCollection<T> = remember(builder) {
|
||||
build(coordinateSpace, builder)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.circle(
|
||||
center: T,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: Dp = 5.dp,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<CircleFeature<T>> = feature(
|
||||
id, CircleFeature(space, center, zoomRange, size, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.rectangle(
|
||||
centerCoordinates: T,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: DpSize = DpSize(5.dp, 5.dp),
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<RectangleFeature<T>> = feature(
|
||||
id, RectangleFeature(space, centerCoordinates, zoomRange, size, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.draw(
|
||||
position: T,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
draw: DrawScope.() -> Unit,
|
||||
): FeatureId<DrawFeature<T>> = feature(
|
||||
id,
|
||||
DrawFeature(space, position, zoomRange, drawFeature = draw)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.line(
|
||||
aCoordinates: T,
|
||||
bCoordinates: T,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<LineFeature<T>> = feature(
|
||||
id,
|
||||
LineFeature(space, aCoordinates, bCoordinates, zoomRange, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.arc(
|
||||
oval: Rectangle<T>,
|
||||
startAngle: Float,
|
||||
arcLength: Float,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<ArcFeature<T>> = feature(
|
||||
id,
|
||||
ArcFeature(space, oval, startAngle, arcLength, zoomRange, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.points(
|
||||
points: List<T>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
stroke: Float = 2f,
|
||||
color: Color = defaultColor,
|
||||
pointMode: PointMode = PointMode.Points,
|
||||
attributes: Attributes = Attributes.EMPTY,
|
||||
id: String? = null,
|
||||
): FeatureId<PointsFeature<T>> = feature(
|
||||
id,
|
||||
PointsFeature(space, points, zoomRange, stroke, color, pointMode, attributes)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.polygon(
|
||||
points: List<T>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<PolygonFeature<T>> = feature(
|
||||
id,
|
||||
PolygonFeature(space, points, zoomRange, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.image(
|
||||
position: T,
|
||||
image: ImageVector,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: DpSize = DpSize(image.defaultWidth, image.defaultHeight),
|
||||
id: String? = null,
|
||||
): FeatureId<VectorImageFeature<T>> =
|
||||
feature(
|
||||
id,
|
||||
VectorImageFeature(
|
||||
space,
|
||||
position,
|
||||
size,
|
||||
image,
|
||||
zoomRange
|
||||
)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.group(
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
builder: FeatureCollection<T>.() -> Unit,
|
||||
): FeatureId<FeatureGroup<T>> {
|
||||
val map = FeatureCollection(space).apply(builder).features
|
||||
val feature = FeatureGroup(space, map, zoomRange)
|
||||
return feature(id, feature)
|
||||
}
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.scalableImage(
|
||||
box: Rectangle<T>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
painter: @Composable () -> Painter,
|
||||
): FeatureId<ScalableImageFeature<T>> = feature(
|
||||
id,
|
||||
ScalableImageFeature<T>(space, box, zoomRange, painter = painter)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.text(
|
||||
position: T,
|
||||
text: String,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
font: FeatureFont.() -> Unit = { size = 16f },
|
||||
id: String? = null,
|
||||
): FeatureId<TextFeature<T>> = feature(
|
||||
id,
|
||||
TextFeature(space, position, text, zoomRange, color, fontConfig = font)
|
||||
)
|
@ -1,37 +1,315 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
///**
|
||||
// * A group of other features
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PointMode
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
@JvmInline
|
||||
public value class FeatureId<out F : Feature<*>>(public val id: String)
|
||||
|
||||
/**
|
||||
* A group of other features
|
||||
*/
|
||||
public data class FeatureGroup<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val featureMap: SnapshotStateMap<String, Feature<T>> = mutableStateMapOf(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : CoordinateSpace<T> by space, Feature<T> {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public operator fun <F : Feature<T>> get(id: FeatureId<F>): F =
|
||||
featureMap[id.id]?.let { it as F } ?: error("Feature with id=$id not found")
|
||||
|
||||
private var uidCounter = 0
|
||||
|
||||
private fun generateUID(feature: Feature<T>?): String = if (feature == null) {
|
||||
"@group[${uidCounter++}]"
|
||||
} else {
|
||||
"@${feature::class.simpleName}[${uidCounter++}]"
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>> feature(id: String?, feature: F): FeatureId<F> {
|
||||
val safeId = id ?: generateUID(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 val features: Collection<Feature<T>> get() = featureMap.values.sortedByDescending { it.z }
|
||||
|
||||
public fun visit(visitor: FeatureGroup<T>.(id: FeatureId<Feature<T>>, feature: Feature<T>) -> Unit) {
|
||||
featureMap.forEach { (key, feature) ->
|
||||
if (feature is FeatureGroup<T>) {
|
||||
feature.visit(visitor)
|
||||
} else {
|
||||
visitor(this, FeatureId(key), feature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <A> getAttribute(id: FeatureId<Feature<T>>, key: Attribute<A>): A? =
|
||||
get(id).attributes[key]
|
||||
|
||||
/**
|
||||
* Process all features with a given attribute from the one with highest [z] to lowest
|
||||
*/
|
||||
public inline fun <A> forEachWithAttribute(
|
||||
key: Attribute<A>,
|
||||
block: (id: FeatureId<*>, feature: Feature<T>, attributeValue: A) -> Unit,
|
||||
) {
|
||||
featureMap.entries.sortedByDescending { it.value.z }.forEach { (id, feature) ->
|
||||
feature.attributes[key]?.let {
|
||||
block(FeatureId<Feature<T>>(id), feature, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.modifyAttributes(modify: Attributes.() -> Attributes) {
|
||||
feature(this, get(this).withAttributes(modify))
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.withAttribute(key: Attribute<V>, value: V?): FeatureId<F> {
|
||||
feature(this, get(this).withAttributes { withAttribute(key, value) })
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add drag to this feature
|
||||
*
|
||||
* @param constraint optional drag constraint
|
||||
*
|
||||
* TODO use context receiver for that
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun FeatureId<DraggableFeature<T>>.draggable(
|
||||
constraint: ((T) -> T)? = null,
|
||||
listener: (PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit)? = null,
|
||||
) {
|
||||
if (getAttribute(this, DraggableAttribute) == null) {
|
||||
val handle = DragHandle.withPrimaryButton<Any> { event, start, end ->
|
||||
val feature = featureMap[id] as? DraggableFeature<T> ?: return@withPrimaryButton DragResult(end)
|
||||
start as ViewPoint<T>
|
||||
end as ViewPoint<T>
|
||||
if (start in feature) {
|
||||
val finalPosition = constraint?.invoke(end.focus) ?: end.focus
|
||||
feature(id, feature.withCoordinates(finalPosition))
|
||||
feature.attributes[DragListenerAttribute]?.forEach {
|
||||
it.handle(event, start, ViewPoint(finalPosition, end.zoom))
|
||||
}
|
||||
DragResult(ViewPoint(finalPosition, end.zoom), false)
|
||||
} else {
|
||||
DragResult(end, true)
|
||||
}
|
||||
}
|
||||
this.withAttribute(DraggableAttribute, handle)
|
||||
}
|
||||
|
||||
//Apply callback
|
||||
if (listener != null) {
|
||||
onDrag(listener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun FeatureId<DraggableFeature<T>>.onDrag(
|
||||
listener: PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
withAttribute(
|
||||
DragListenerAttribute,
|
||||
(getAttribute(this, DragListenerAttribute) ?: emptySet()) +
|
||||
DragListener { event, from, to ->
|
||||
event.listener(from as ViewPoint<T>, to as ViewPoint<T>)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <F : DomainFeature<T>> FeatureId<F>.onClick(
|
||||
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
withAttribute(
|
||||
ClickListenerAttribute,
|
||||
(getAttribute(this, ClickListenerAttribute) ?: emptySet()) +
|
||||
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <F : DomainFeature<T>> FeatureId<F>.onHover(
|
||||
onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
withAttribute(
|
||||
HoverListenerAttribute,
|
||||
(getAttribute(this, HoverListenerAttribute) ?: emptySet()) +
|
||||
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
||||
)
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Cyclic update of a feature. Called infinitely until canceled.
|
||||
// */
|
||||
//public data class FeatureGroup<T : Any>(
|
||||
// val parentBuilder: FeatureBuilder<T>,
|
||||
// private val groupId: String,
|
||||
// public override val zoomRange: FloatRange,
|
||||
// override val attributes: Attributes = Attributes.EMPTY,
|
||||
//) : FeatureBuilder<T>, Feature<T> {
|
||||
//
|
||||
// override val space: CoordinateSpace<T> get() = parentBuilder.space
|
||||
//
|
||||
// override fun generateUID(feature: Feature<T>?): String = parentBuilder.generateUID(feature)
|
||||
//
|
||||
// override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureId<F> =
|
||||
// parentBuilder.feature("${groupId}.${id ?: parentBuilder.generateUID(feature)}", feature)
|
||||
//
|
||||
// override fun <F : Feature<T>, V> FeatureId<F>.withAttribute(key: Attribute<V>, value: V?): FeatureId<F> =
|
||||
// with(parentBuilder) {
|
||||
// FeatureId<F>("${groupId}.${this@withAttribute.id}").withAttribute(key, value)
|
||||
// public fun <F : Feature<T>> FeatureId<F>.updated(
|
||||
// scope: CoroutineScope,
|
||||
// update: suspend (F) -> F,
|
||||
// ): Job = scope.launch {
|
||||
// while (isActive) {
|
||||
// feature(this@updated, update(get(this@updated)))
|
||||
// }
|
||||
//
|
||||
// override fun getBoundingBox(zoom: Float): Rectangle<T>? {
|
||||
// TODO("Not yet implemented")
|
||||
// }
|
||||
//
|
||||
// override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> =
|
||||
// copy(attributes = attributes.modify())
|
||||
//}
|
||||
//
|
||||
//public fun <T : Any> FeatureBuilder<T>.group(
|
||||
// zoomRange: FloatRange = defaultZoomRange,
|
||||
// id: String? = null,
|
||||
// builder: FeatureBuilder<T>.() -> Unit,
|
||||
//): FeatureId<FeatureGroup<T>> = feature(id, FeatureGroup(this, id ?: generateUID(null), zoomRange).apply(builder))
|
||||
|
||||
public fun <F : Feature<T>> FeatureId<F>.color(color: Color): FeatureId<F> =
|
||||
withAttribute(ColorAttribute, color)
|
||||
public fun <F : Feature<T>> FeatureId<F>.zoomRange(range: FloatRange): FeatureId<F> =
|
||||
withAttribute(ZoomRangeAttribute, range)
|
||||
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
|
||||
featureMap.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
|
||||
}
|
||||
|
||||
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = attributes)
|
||||
|
||||
public companion object {
|
||||
|
||||
/**
|
||||
* Build, but do not remember map feature state
|
||||
*/
|
||||
public fun <T : Any> build(
|
||||
coordinateSpace: CoordinateSpace<T>,
|
||||
builder: FeatureGroup<T>.() -> Unit = {},
|
||||
): FeatureGroup<T> = FeatureGroup(coordinateSpace).apply(builder)
|
||||
|
||||
/**
|
||||
* Build and remember map feature state
|
||||
*/
|
||||
@Composable
|
||||
public fun <T : Any> remember(
|
||||
coordinateSpace: CoordinateSpace<T>,
|
||||
builder: FeatureGroup<T>.() -> Unit = {},
|
||||
): FeatureGroup<T> = remember(builder) {
|
||||
build(coordinateSpace, builder)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.circle(
|
||||
center: T,
|
||||
size: Dp = 5.dp,
|
||||
id: String? = null,
|
||||
): FeatureId<CircleFeature<T>> = feature(
|
||||
id, CircleFeature(space, center, size)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.rectangle(
|
||||
centerCoordinates: T,
|
||||
size: DpSize = DpSize(5.dp, 5.dp),
|
||||
id: String? = null,
|
||||
): FeatureId<RectangleFeature<T>> = feature(
|
||||
id, RectangleFeature(space, centerCoordinates, size)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.draw(
|
||||
position: T,
|
||||
id: String? = null,
|
||||
draw: DrawScope.() -> Unit,
|
||||
): FeatureId<DrawFeature<T>> = feature(
|
||||
id,
|
||||
DrawFeature(space, position, drawFeature = draw)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.line(
|
||||
aCoordinates: T,
|
||||
bCoordinates: T,
|
||||
id: String? = null,
|
||||
): FeatureId<LineFeature<T>> = feature(
|
||||
id,
|
||||
LineFeature(space, aCoordinates, bCoordinates)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.arc(
|
||||
oval: Rectangle<T>,
|
||||
startAngle: Float,
|
||||
arcLength: Float,
|
||||
id: String? = null,
|
||||
): FeatureId<ArcFeature<T>> = feature(
|
||||
id,
|
||||
ArcFeature(space, oval, startAngle, arcLength)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.points(
|
||||
points: List<T>,
|
||||
stroke: Float = 2f,
|
||||
pointMode: PointMode = PointMode.Points,
|
||||
attributes: Attributes = Attributes.EMPTY,
|
||||
id: String? = null,
|
||||
): FeatureId<PointsFeature<T>> = feature(
|
||||
id,
|
||||
PointsFeature(space, points, stroke, pointMode, attributes)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.polygon(
|
||||
points: List<T>,
|
||||
id: String? = null,
|
||||
): FeatureId<PolygonFeature<T>> = feature(
|
||||
id,
|
||||
PolygonFeature(space, points)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.image(
|
||||
position: T,
|
||||
image: ImageVector,
|
||||
size: DpSize = DpSize(image.defaultWidth, image.defaultHeight),
|
||||
id: String? = null,
|
||||
): FeatureId<VectorImageFeature<T>> =
|
||||
feature(
|
||||
id,
|
||||
VectorImageFeature(
|
||||
space,
|
||||
position,
|
||||
size,
|
||||
image,
|
||||
)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.group(
|
||||
id: String? = null,
|
||||
builder: FeatureGroup<T>.() -> Unit,
|
||||
): FeatureId<FeatureGroup<T>> {
|
||||
val collection = FeatureGroup(space).apply(builder)
|
||||
val feature = FeatureGroup(space, collection.featureMap)
|
||||
return feature(id, feature)
|
||||
}
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.scalableImage(
|
||||
box: Rectangle<T>,
|
||||
id: String? = null,
|
||||
painter: @Composable () -> Painter,
|
||||
): FeatureId<ScalableImageFeature<T>> = feature(
|
||||
id,
|
||||
ScalableImageFeature<T>(space, box, painter = painter)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.text(
|
||||
position: T,
|
||||
text: String,
|
||||
font: FeatureFont.() -> Unit = { size = 16f },
|
||||
id: String? = null,
|
||||
): FeatureId<TextFeature<T>> = feature(
|
||||
id,
|
||||
TextFeature(space, position, text, fontConfig = font)
|
||||
)
|
||||
|
@ -1,13 +1,9 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
|
||||
public fun <T : Any> FeatureCollection<T>.draggableLine(
|
||||
public fun <T : Any> FeatureGroup<T>.draggableLine(
|
||||
aId: FeatureId<MarkerFeature<T>>,
|
||||
bId: FeatureId<MarkerFeature<T>>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = Color.Red,
|
||||
id: String? = null,
|
||||
): FeatureId<LineFeature<T>> {
|
||||
var lineId: FeatureId<LineFeature<T>>? = null
|
||||
@ -15,8 +11,6 @@ public fun <T : Any> FeatureCollection<T>.draggableLine(
|
||||
fun drawLine(): FeatureId<LineFeature<T>> = line(
|
||||
get(aId).center,
|
||||
get(bId).center,
|
||||
zoomRange,
|
||||
color,
|
||||
lineId?.id ?: id
|
||||
).also {
|
||||
lineId = it
|
||||
|
@ -14,10 +14,11 @@ import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Create a modifier for Map/Scheme canvas controls on desktop
|
||||
* @param features a collection of features to be rendered in descending [ZAttribute] order
|
||||
*/
|
||||
public fun <T : Any> Modifier.mapControls(
|
||||
state: CoordinateViewScope<T>,
|
||||
features: Map<FeatureId<*>, Feature<T>>,
|
||||
features: Collection<Feature<T>>,
|
||||
): Modifier = with(state) {
|
||||
pointerInput(Unit) {
|
||||
fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp())
|
||||
@ -27,10 +28,8 @@ public fun <T : Any> Modifier.mapControls(
|
||||
val coordinates = event.changes.first().position.toDpOffset().toCoordinates()
|
||||
val point = space.ViewPoint(coordinates, zoom)
|
||||
|
||||
val sortedFeatures =features.values.sortedByDescending { it.z }
|
||||
|
||||
if (event.type == PointerEventType.Move) {
|
||||
for (feature in sortedFeatures) {
|
||||
for (feature in features) {
|
||||
val listeners = (feature as? DomainFeature)?.attributes?.get(HoverListenerAttribute)
|
||||
if (listeners != null && point in feature) {
|
||||
listeners.forEach { it.handle(event, point) }
|
||||
@ -43,7 +42,7 @@ public fun <T : Any> Modifier.mapControls(
|
||||
event,
|
||||
point
|
||||
)
|
||||
for (feature in sortedFeatures) {
|
||||
for (feature in features) {
|
||||
val listeners = (feature as? DomainFeature)?.attributes?.get(ClickListenerAttribute)
|
||||
if (listeners != null && point in feature) {
|
||||
listeners.forEach { it.handle(event, point) }
|
||||
@ -97,9 +96,8 @@ public fun <T : Any> Modifier.mapControls(
|
||||
val dragResult = config.dragHandle?.handle(event, dragStart, dragEnd)
|
||||
if (dragResult?.handleNext == false) return@drag
|
||||
|
||||
features.values.asSequence()
|
||||
features.asSequence()
|
||||
.filterIsInstance<DraggableFeature<T>>()
|
||||
.sortedByDescending { it.z }
|
||||
.mapNotNull {
|
||||
it.attributes[DraggableAttribute]
|
||||
}.forEach { handler ->
|
||||
|
@ -25,21 +25,21 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
state: CoordinateViewScope<T>,
|
||||
painterCache: Map<PainterFeature<T>, Painter>,
|
||||
feature: Feature<T>,
|
||||
|
||||
): Unit = with(state) {
|
||||
val color = feature.color ?: Color.Red
|
||||
val alpha = feature.attributes[AlphaAttribute]?:1f
|
||||
fun T.toOffset(): Offset = toOffset(this@drawFeature)
|
||||
|
||||
when (feature) {
|
||||
is FeatureSelector -> drawFeature(state, painterCache, feature.selector(state.zoom))
|
||||
is CircleFeature -> drawCircle(
|
||||
feature.color,
|
||||
color,
|
||||
feature.size.toPx(),
|
||||
center = feature.center.toOffset()
|
||||
)
|
||||
|
||||
is RectangleFeature -> drawRect(
|
||||
feature.color,
|
||||
color,
|
||||
topLeft = feature.center.toOffset() - Offset(
|
||||
feature.size.width.toPx() / 2,
|
||||
feature.size.height.toPx() / 2
|
||||
@ -47,14 +47,14 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
size = feature.size.toSize()
|
||||
)
|
||||
|
||||
is LineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
|
||||
is LineFeature -> drawLine(color, feature.a.toOffset(), feature.b.toOffset())
|
||||
is ArcFeature -> {
|
||||
val dpRect = feature.oval.toDpRect().toRect()
|
||||
|
||||
val size = Size(dpRect.width, dpRect.height)
|
||||
|
||||
drawArc(
|
||||
color = feature.color,
|
||||
color = color,
|
||||
startAngle = feature.startAngle / PI.toFloat() * 180f,
|
||||
sweepAngle = feature.arcLength / PI.toFloat() * 180f,
|
||||
useCenter = false,
|
||||
@ -85,7 +85,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
offset.x + 5,
|
||||
offset.y - 5,
|
||||
Font().apply(feature.fontConfig),
|
||||
feature.color.toPaint()
|
||||
(feature.color ?: Color.Black).toPaint()
|
||||
)
|
||||
}
|
||||
|
||||
@ -97,8 +97,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
}
|
||||
|
||||
is FeatureGroup -> {
|
||||
//do nothing
|
||||
feature.children.values.forEach {
|
||||
feature.featureMap.values.forEach {
|
||||
drawFeature(state, painterCache, it)
|
||||
}
|
||||
}
|
||||
@ -116,7 +115,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
val points = feature.points.map { it.toOffset() }
|
||||
drawPoints(
|
||||
points = points,
|
||||
color = feature.color,
|
||||
color = color,
|
||||
strokeWidth = feature.stroke,
|
||||
pointMode = feature.pointMode,
|
||||
alpha = alpha
|
||||
@ -133,7 +132,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
}
|
||||
drawPath(
|
||||
path = polygonPath,
|
||||
color = feature.color,
|
||||
color = color,
|
||||
alpha = alpha
|
||||
)
|
||||
}
|
||||
|
@ -12,14 +12,12 @@ import kotlinx.serialization.json.jsonPrimitive
|
||||
/**
|
||||
* Add a single Json geometry to a feature builder
|
||||
*/
|
||||
public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
||||
public fun FeatureGroup<Gmc>.geoJsonGeometry(
|
||||
geometry: GeoJsonGeometry,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<Feature<Gmc>> = when (geometry) {
|
||||
is GeoJsonLineString -> points(
|
||||
geometry.coordinates,
|
||||
color = color,
|
||||
pointMode = PointMode.Lines
|
||||
)
|
||||
|
||||
@ -27,7 +25,6 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
||||
geometry.coordinates.forEach {
|
||||
points(
|
||||
it,
|
||||
color = color,
|
||||
pointMode = PointMode.Lines
|
||||
)
|
||||
}
|
||||
@ -35,7 +32,6 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
||||
|
||||
is GeoJsonMultiPoint -> points(
|
||||
geometry.coordinates,
|
||||
color = color,
|
||||
pointMode = PointMode.Points
|
||||
)
|
||||
|
||||
@ -43,15 +39,13 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
||||
geometry.coordinates.forEach {
|
||||
polygon(
|
||||
it.first(),
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is GeoJsonPoint -> circle(geometry.coordinates, color = color, id = id)
|
||||
is GeoJsonPoint -> circle(geometry.coordinates, id = id)
|
||||
is GeoJsonPolygon -> polygon(
|
||||
geometry.coordinates.first(),
|
||||
color = color,
|
||||
)
|
||||
|
||||
is GeoJsonGeometryCollection -> group(id = id) {
|
||||
@ -63,18 +57,22 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
||||
withAttribute(AlphaAttribute, 0.5f)
|
||||
}
|
||||
|
||||
public fun FeatureBuilder<Gmc>.geoJsonFeature(
|
||||
public fun FeatureGroup<Gmc>.geoJsonFeature(
|
||||
geoJson: GeoJsonFeature,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<Feature<Gmc>>? {
|
||||
val geometry = geoJson.geometry ?: return null
|
||||
val idOverride = geoJson.properties?.get("id")?.jsonPrimitive?.contentOrNull ?: id
|
||||
val colorOverride = geoJson.properties?.get("color")?.jsonPrimitive?.intOrNull?.let { Color(it) } ?: color
|
||||
return geoJsonGeometry(geometry, colorOverride, idOverride)
|
||||
val colorOverride = geoJson.properties?.get("color")?.jsonPrimitive?.intOrNull?.let { Color(it) }
|
||||
val jsonGeometry = geoJsonGeometry(geometry, idOverride)
|
||||
return if( colorOverride!= null){
|
||||
jsonGeometry.color(colorOverride)
|
||||
} else{
|
||||
jsonGeometry
|
||||
}
|
||||
}
|
||||
|
||||
public fun FeatureBuilder<Gmc>.geoJson(
|
||||
public fun FeatureGroup<Gmc>.geoJson(
|
||||
geoJson: GeoJson,
|
||||
id: String? = null,
|
||||
): FeatureId<Feature<Gmc>>? = when (geoJson) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package center.sciprog.maps.geojson
|
||||
|
||||
import center.sciprog.maps.coordinates.Gmc
|
||||
import center.sciprog.maps.features.FeatureBuilder
|
||||
import center.sciprog.maps.features.FeatureGroup
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import java.net.URL
|
||||
@ -9,7 +9,7 @@ import java.net.URL
|
||||
/**
|
||||
* Add geojson features from url
|
||||
*/
|
||||
public fun FeatureBuilder<Gmc>.geoJson(
|
||||
public fun FeatureGroup<Gmc>.geoJson(
|
||||
geoJsonUrl: URL,
|
||||
id: String? = null,
|
||||
) {
|
||||
|
@ -12,7 +12,7 @@ import center.sciprog.maps.features.*
|
||||
|
||||
internal fun Pair<Number, Number>.toCoordinates(): XY = XY(first.toFloat(), second.toFloat())
|
||||
|
||||
fun FeatureBuilder<XY>.background(
|
||||
fun FeatureGroup<XY>.background(
|
||||
width: Float,
|
||||
height: Float,
|
||||
offset: XY = XY(0f, 0f),
|
||||
@ -35,7 +35,7 @@ fun FeatureBuilder<XY>.background(
|
||||
)
|
||||
}
|
||||
|
||||
fun FeatureBuilder<XY>.circle(
|
||||
fun FeatureGroup<XY>.circle(
|
||||
centerCoordinates: Pair<Number, Number>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: Dp = 5.dp,
|
||||
@ -43,14 +43,14 @@ fun FeatureBuilder<XY>.circle(
|
||||
id: String? = null,
|
||||
): FeatureId<CircleFeature<XY>> = circle(centerCoordinates.toCoordinates(), zoomRange, size, color, id = id)
|
||||
|
||||
fun FeatureBuilder<XY>.draw(
|
||||
fun FeatureGroup<XY>.draw(
|
||||
position: Pair<Number, Number>,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
draw: DrawScope.() -> Unit,
|
||||
): FeatureId<DrawFeature<XY>> = draw(position.toCoordinates(), zoomRange = zoomRange, id = id, draw = draw)
|
||||
|
||||
fun FeatureBuilder<XY>.line(
|
||||
fun FeatureGroup<XY>.line(
|
||||
aCoordinates: Pair<Number, Number>,
|
||||
bCoordinates: Pair<Number, Number>,
|
||||
scaleRange: FloatRange = defaultZoomRange,
|
||||
@ -59,7 +59,7 @@ fun FeatureBuilder<XY>.line(
|
||||
): FeatureId<LineFeature<XY>> = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), scaleRange, color, id)
|
||||
|
||||
|
||||
public fun FeatureBuilder<XY>.arc(
|
||||
public fun FeatureGroup<XY>.arc(
|
||||
center: Pair<Double, Double>,
|
||||
radius: Float,
|
||||
startAngle: Float,
|
||||
@ -76,7 +76,7 @@ public fun FeatureBuilder<XY>.arc(
|
||||
id = id
|
||||
)
|
||||
|
||||
fun FeatureBuilder<XY>.image(
|
||||
fun FeatureGroup<XY>.image(
|
||||
position: Pair<Number, Number>,
|
||||
image: ImageVector,
|
||||
size: DpSize = DpSize(image.defaultWidth, image.defaultHeight),
|
||||
@ -85,7 +85,7 @@ fun FeatureBuilder<XY>.image(
|
||||
): FeatureId<VectorImageFeature<XY>> =
|
||||
image(position.toCoordinates(), image, size = size, zoomRange = zoomRange, id = id)
|
||||
|
||||
fun FeatureBuilder<XY>.text(
|
||||
fun FeatureGroup<XY>.text(
|
||||
position: Pair<Number, Number>,
|
||||
text: String,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
|
@ -22,12 +22,12 @@ private val logger = KotlinLogging.logger("SchemeView")
|
||||
@Composable
|
||||
public fun SchemeView(
|
||||
state: XYViewScope,
|
||||
featuresState: FeatureCollection<XY>,
|
||||
featuresState: FeatureGroup<XY>,
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
with(state) {
|
||||
val painterCache: Map<PainterFeature<XY>, Painter> = key(featuresState) {
|
||||
featuresState.features.values.filterIsInstance<PainterFeature<XY>>().associateWith { it.getPainter() }
|
||||
featuresState.features.filterIsInstance<PainterFeature<XY>>().associateWith { it.getPainter() }
|
||||
}
|
||||
|
||||
Canvas(modifier = modifier.mapControls(state, featuresState.features).fillMaxSize()) {
|
||||
@ -38,9 +38,8 @@ public fun SchemeView(
|
||||
}
|
||||
|
||||
clipRect {
|
||||
featuresState.features.values
|
||||
featuresState.features
|
||||
.filter { viewPoint.zoom in it.zoomRange }
|
||||
.sortedBy { it.z }
|
||||
.forEach { background ->
|
||||
drawFeature(state, painterCache, background)
|
||||
}
|
||||
@ -88,7 +87,7 @@ public fun SchemeView(
|
||||
|
||||
|
||||
val featureState = key(featureMap) {
|
||||
FeatureCollection.build(XYCoordinateSpace) {
|
||||
FeatureGroup.build(XYCoordinateSpace) {
|
||||
featureMap.forEach { feature(it.key.id, it.value) }
|
||||
}
|
||||
}
|
||||
@ -96,7 +95,7 @@ public fun SchemeView(
|
||||
val state = rememberMapState(
|
||||
config,
|
||||
initialViewPoint = initialViewPoint,
|
||||
initialRectangle = initialRectangle ?: featureState.features.values.computeBoundingBox(XYCoordinateSpace, Float.MAX_VALUE),
|
||||
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(XYCoordinateSpace, Float.MAX_VALUE),
|
||||
)
|
||||
|
||||
SchemeView(state, featureState, modifier)
|
||||
@ -115,13 +114,13 @@ public fun SchemeView(
|
||||
initialRectangle: Rectangle<XY>? = null,
|
||||
config: ViewConfig<XY> = ViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
buildFeatures: FeatureCollection<XY>.() -> Unit = {},
|
||||
buildFeatures: FeatureGroup<XY>.() -> Unit = {},
|
||||
) {
|
||||
val featureState = FeatureCollection.remember(XYCoordinateSpace, buildFeatures)
|
||||
val featureState = FeatureGroup.remember(XYCoordinateSpace, buildFeatures)
|
||||
val mapState: XYViewScope = rememberMapState(
|
||||
config,
|
||||
initialViewPoint = initialViewPoint,
|
||||
initialRectangle = initialRectangle ?: featureState.features.values.computeBoundingBox(XYCoordinateSpace, Float.MAX_VALUE),
|
||||
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(XYCoordinateSpace, Float.MAX_VALUE),
|
||||
)
|
||||
|
||||
SchemeView(mapState, featureState, modifier)
|
||||
|
@ -17,14 +17,14 @@ import kotlin.math.abs
|
||||
|
||||
|
||||
class FeatureStateSnapshot<T : Any>(
|
||||
val features: Map<FeatureId<*>, Feature<T>>,
|
||||
val features: Map<String, Feature<T>>,
|
||||
val painterCache: Map<PainterFeature<T>, Painter>,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun <T: Any> FeatureCollection<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
|
||||
features,
|
||||
features.values.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
|
||||
fun <T: Any> FeatureGroup<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
|
||||
featureMap,
|
||||
features.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
|
||||
)
|
||||
|
||||
fun FeatureStateSnapshot<XY>.generateSvg(
|
||||
@ -114,7 +114,7 @@ fun FeatureStateSnapshot<XY>.generateSvg(
|
||||
}
|
||||
|
||||
is FeatureGroup -> {
|
||||
feature.children.values.forEach {
|
||||
feature.featureMap.values.forEach {
|
||||
drawFeature(scale, it)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user