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.filterNotNull
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
@ -64,13 +66,13 @@ fun App() {
|
|||||||
|
|
||||||
image(pointOne, Icons.Filled.Home)
|
image(pointOne, Icons.Filled.Home)
|
||||||
|
|
||||||
val marker1 = rectangle(55.744 to 38.614, 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 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 marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
|
||||||
|
|
||||||
draggableLine(marker1, marker2, color = Color.Blue)
|
draggableLine(marker1, marker2).color(Color.Blue)
|
||||||
draggableLine(marker2, marker3, color = Color.Blue)
|
draggableLine(marker2, marker3).color(Color.Blue)
|
||||||
draggableLine(marker3, marker1, color = Color.Blue)
|
draggableLine(marker3, marker1).color(Color.Blue)
|
||||||
|
|
||||||
points(
|
points(
|
||||||
points = listOf(
|
points = listOf(
|
||||||
@ -85,13 +87,16 @@ fun App() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
//remember feature ID
|
//remember feature ID
|
||||||
circle(
|
val circleId = circle(
|
||||||
centerCoordinates = pointTwo,
|
centerCoordinates = pointTwo,
|
||||||
).updated(scope) {
|
)
|
||||||
|
scope.launch {
|
||||||
|
while (isActive) {
|
||||||
delay(200)
|
delay(200)
|
||||||
//Overwrite a feature with new color
|
circleId.color(Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()))
|
||||||
it.copy(color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()))
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// draw(position = pointThree) {
|
// draw(position = pointThree) {
|
||||||
// drawLine(start = Offset(-10f, -10f), end = Offset(10f, 10f), color = Color.Red)
|
// drawLine(start = Offset(-10f, -10f), end = Offset(10f, 10f), color = Color.Red)
|
||||||
@ -105,16 +110,20 @@ fun App() {
|
|||||||
|
|
||||||
centerCoordinates.filterNotNull().onEach {
|
centerCoordinates.filterNotNull().onEach {
|
||||||
group(id = "center") {
|
group(id = "center") {
|
||||||
circle(center = it, color = Color.Blue, id = "circle", size = 1.dp)
|
circle(center = it, id = "circle", size = 1.dp).color(Color.Blue)
|
||||||
text(position = it, it.toShortString(), id = "text", color = Color.Blue)
|
text(position = it, it.toShortString(), id = "text").color(Color.Blue)
|
||||||
}
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
features.forEach { (id, feature) ->
|
visit { id, feature ->
|
||||||
if (feature is PolygonFeature) {
|
if (feature is PolygonFeature) {
|
||||||
(id as FeatureId<PolygonFeature<Gmc>>).onHover {
|
(id as FeatureId<PolygonFeature<Gmc>>).onHover {
|
||||||
println("Hover on $id")
|
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.res.painterResource
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.application
|
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.ViewConfig
|
||||||
import center.sciprog.maps.features.ViewPoint
|
import center.sciprog.maps.features.ViewPoint
|
||||||
import center.sciprog.maps.features.computeBoundingBox
|
import center.sciprog.maps.features.computeBoundingBox
|
||||||
@ -29,7 +29,7 @@ fun App() {
|
|||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val schemeFeaturesState = FeatureCollection.remember(XYCoordinateSpace) {
|
val schemeFeaturesState = FeatureGroup.remember(XYCoordinateSpace) {
|
||||||
background(1600f, 1200f) { painterResource("middle-earth.jpg") }
|
background(1600f, 1200f) { painterResource("middle-earth.jpg") }
|
||||||
circle(410.52737 to 868.7676, color = Color.Blue)
|
circle(410.52737 to 868.7676, color = Color.Blue)
|
||||||
text(410.52737 to 868.7676, "Shire", color = Color.Blue)
|
text(410.52737 to 868.7676, "Shire", color = Color.Blue)
|
||||||
@ -53,7 +53,7 @@ fun App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val initialViewPoint: ViewPoint<XY> = remember {
|
val initialViewPoint: ViewPoint<XY> = remember {
|
||||||
schemeFeaturesState.features.values.computeBoundingBox(XYCoordinateSpace, 1f)?.computeViewPoint()
|
schemeFeaturesState.features.computeBoundingBox(XYCoordinateSpace, 1f)?.computeViewPoint()
|
||||||
?: XYViewPoint(XY(0f, 0f))
|
?: XYViewPoint(XY(0f, 0f))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import center.sciprog.maps.features.*
|
|||||||
@Composable
|
@Composable
|
||||||
public expect fun MapView(
|
public expect fun MapView(
|
||||||
mapState: MapViewScope,
|
mapState: MapViewScope,
|
||||||
featuresState: FeatureCollection<Gmc>,
|
featuresState: FeatureGroup<Gmc>,
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ public fun MapView(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
val featureState = remember(featureMap) {
|
val featureState = remember(featureMap) {
|
||||||
FeatureCollection.build(WebMercatorSpace) {
|
FeatureGroup.build(WebMercatorSpace) {
|
||||||
featureMap.forEach { feature(it.key.id, it.value) }
|
featureMap.forEach { feature(it.key.id, it.value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ public fun MapView(
|
|||||||
mapTileProvider,
|
mapTileProvider,
|
||||||
config,
|
config,
|
||||||
initialViewPoint = initialViewPoint,
|
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)
|
MapView(mapState, featureState, modifier)
|
||||||
@ -58,16 +58,16 @@ public fun MapView(
|
|||||||
initialRectangle: Rectangle<Gmc>? = null,
|
initialRectangle: Rectangle<Gmc>? = null,
|
||||||
config: ViewConfig<Gmc> = ViewConfig(),
|
config: ViewConfig<Gmc> = ViewConfig(),
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
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(
|
val mapState: MapViewScope = rememberMapState(
|
||||||
mapTileProvider,
|
mapTileProvider,
|
||||||
config,
|
config,
|
||||||
initialViewPoint = initialViewPoint,
|
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)
|
MapView(mapState, featureState, modifier)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package center.sciprog.maps.compose
|
package center.sciprog.maps.compose
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.PointMode
|
import androidx.compose.ui.graphics.PointMode
|
||||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@ -11,73 +10,62 @@ import center.sciprog.maps.coordinates.*
|
|||||||
import center.sciprog.maps.features.*
|
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())
|
GeodeticMapCoordinates.ofDegrees(pair.first.toDouble(), pair.second.toDouble())
|
||||||
|
|
||||||
public typealias MapFeature = Feature<Gmc>
|
public typealias MapFeature = Feature<Gmc>
|
||||||
|
|
||||||
public fun FeatureBuilder<Gmc>.circle(
|
public fun FeatureGroup<Gmc>.circle(
|
||||||
centerCoordinates: Pair<Number, Number>,
|
centerCoordinates: Pair<Number, Number>,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
size: Dp = 5.dp,
|
size: Dp = 5.dp,
|
||||||
color: Color = defaultColor,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<CircleFeature<Gmc>> = feature(
|
): 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>,
|
centerCoordinates: Pair<Number, Number>,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
size: DpSize = DpSize(5.dp, 5.dp),
|
size: DpSize = DpSize(5.dp, 5.dp),
|
||||||
color: Color = defaultColor,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<RectangleFeature<Gmc>> = feature(
|
): 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>,
|
position: Pair<Number, Number>,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
draw: DrawScope.() -> Unit,
|
draw: DrawScope.() -> Unit,
|
||||||
): FeatureId<DrawFeature<Gmc>> = feature(
|
): FeatureId<DrawFeature<Gmc>> = feature(
|
||||||
id,
|
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,
|
curve: GmcCurve,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
color: Color = defaultColor,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<LineFeature<Gmc>> = feature(
|
): FeatureId<LineFeature<Gmc>> = feature(
|
||||||
id,
|
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>,
|
aCoordinates: Pair<Double, Double>,
|
||||||
bCoordinates: Pair<Double, Double>,
|
bCoordinates: Pair<Double, Double>,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
color: Color = defaultColor,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<LineFeature<Gmc>> = feature(
|
): FeatureId<LineFeature<Gmc>> = feature(
|
||||||
id,
|
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>,
|
center: Pair<Double, Double>,
|
||||||
radius: Distance,
|
radius: Distance,
|
||||||
startAngle: Angle,
|
startAngle: Angle,
|
||||||
arcLength: Angle,
|
arcLength: Angle,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
color: Color = defaultColor,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<ArcFeature<Gmc>> = feature(
|
): FeatureId<ArcFeature<Gmc>> = feature(
|
||||||
id,
|
id,
|
||||||
@ -86,26 +74,21 @@ public fun FeatureBuilder<Gmc>.arc(
|
|||||||
oval = space.Rectangle(coordinatesOf(center), radius, radius),
|
oval = space.Rectangle(coordinatesOf(center), radius, radius),
|
||||||
startAngle = startAngle.radians.toFloat(),
|
startAngle = startAngle.radians.toFloat(),
|
||||||
arcLength = arcLength.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>>,
|
points: List<Pair<Double, Double>>,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
stroke: Float = 2f,
|
stroke: Float = 2f,
|
||||||
color: Color = defaultColor,
|
|
||||||
pointMode: PointMode = PointMode.Points,
|
pointMode: PointMode = PointMode.Points,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<PointsFeature<Gmc>> =
|
): 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>,
|
position: Pair<Double, Double>,
|
||||||
image: ImageVector,
|
image: ImageVector,
|
||||||
size: DpSize = DpSize(20.dp, 20.dp),
|
size: DpSize = DpSize(20.dp, 20.dp),
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<VectorImageFeature<Gmc>> = feature(
|
): FeatureId<VectorImageFeature<Gmc>> = feature(
|
||||||
id,
|
id,
|
||||||
@ -114,18 +97,15 @@ public fun FeatureBuilder<Gmc>.image(
|
|||||||
coordinatesOf(position),
|
coordinatesOf(position),
|
||||||
size,
|
size,
|
||||||
image,
|
image,
|
||||||
zoomRange
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun FeatureBuilder<Gmc>.text(
|
public fun FeatureGroup<Gmc>.text(
|
||||||
position: Pair<Double, Double>,
|
position: Pair<Double, Double>,
|
||||||
text: String,
|
text: String,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
color: Color = defaultColor,
|
|
||||||
font: FeatureFont.() -> Unit = { size = 16f },
|
font: FeatureFont.() -> Unit = { size = 16f },
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<TextFeature<Gmc>> = feature(
|
): FeatureId<TextFeature<Gmc>> = feature(
|
||||||
id,
|
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.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import center.sciprog.maps.coordinates.Gmc
|
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.PainterFeature
|
||||||
import center.sciprog.maps.features.drawFeature
|
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.launch
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlinx.coroutines.supervisorScope
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
@ -44,7 +44,7 @@ private val logger = KotlinLogging.logger("MapView")
|
|||||||
@Composable
|
@Composable
|
||||||
public actual fun MapView(
|
public actual fun MapView(
|
||||||
mapState: MapViewScope,
|
mapState: MapViewScope,
|
||||||
featuresState: FeatureCollection<Gmc>,
|
featuresState: FeatureGroup<Gmc>,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
): Unit = with(mapState) {
|
): Unit = with(mapState) {
|
||||||
|
|
||||||
@ -90,9 +90,10 @@ public actual fun MapView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val painterCache: Map<PainterFeature<Gmc>, Painter> = key(featuresState) {
|
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()) {
|
Canvas(modifier = modifier.mapControls(mapState, featuresState.features).fillMaxSize()) {
|
||||||
|
|
||||||
if (canvasSize != size.toDpSize()) {
|
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 ->
|
.forEach { feature ->
|
||||||
drawFeature(mapState, painterCache, feature)
|
drawFeature(mapState, painterCache, feature)
|
||||||
}
|
}
|
||||||
|
@ -18,4 +18,6 @@ public object VisibleAttribute : Attribute<Boolean>
|
|||||||
|
|
||||||
public object ColorAttribute : Attribute<Color>
|
public object ColorAttribute : Attribute<Color>
|
||||||
|
|
||||||
|
public object ZoomRangeAttribute : Attribute<FloatRange>
|
||||||
|
|
||||||
public object AlphaAttribute : Attribute<Float>
|
public object AlphaAttribute : Attribute<Float>
|
||||||
|
@ -21,8 +21,6 @@ public interface Feature<T : Any> {
|
|||||||
|
|
||||||
public val space: CoordinateSpace<T>
|
public val space: CoordinateSpace<T>
|
||||||
|
|
||||||
public val zoomRange: FloatRange
|
|
||||||
|
|
||||||
public val attributes: Attributes
|
public val attributes: Attributes
|
||||||
|
|
||||||
public fun getBoundingBox(zoom: Float): Rectangle<T>?
|
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 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> {
|
public interface PainterFeature<T : Any> : Feature<T> {
|
||||||
@Composable
|
@Composable
|
||||||
public fun getPainter(): Painter
|
public fun getPainter(): Painter
|
||||||
@ -65,7 +68,6 @@ public fun <T : Any> Iterable<Feature<T>>.computeBoundingBox(
|
|||||||
@Stable
|
@Stable
|
||||||
public data class FeatureSelector<T : Any>(
|
public data class FeatureSelector<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
public val selector: (zoom: Float) -> Feature<T>,
|
public val selector: (zoom: Float) -> Feature<T>,
|
||||||
) : Feature<T> {
|
) : Feature<T> {
|
||||||
@ -81,7 +83,6 @@ public data class PathFeature<T : Any>(
|
|||||||
public val rectangle: Rectangle<T>,
|
public val rectangle: Rectangle<T>,
|
||||||
public val path: Path,
|
public val path: Path,
|
||||||
public val brush: Brush,
|
public val brush: Brush,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
public val style: DrawStyle = Fill,
|
public val style: DrawStyle = Fill,
|
||||||
public val targetRect: Rect = path.getBounds(),
|
public val targetRect: Rect = path.getBounds(),
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
@ -94,7 +95,6 @@ public data class PathFeature<T : Any>(
|
|||||||
brush = brush,
|
brush = brush,
|
||||||
style = style,
|
style = style,
|
||||||
targetRect = targetRect,
|
targetRect = targetRect,
|
||||||
zoomRange = zoomRange
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,9 +106,7 @@ public data class PathFeature<T : Any>(
|
|||||||
public data class PointsFeature<T : Any>(
|
public data class PointsFeature<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
public val points: List<T>,
|
public val points: List<T>,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
public val stroke: Float = 2f,
|
public val stroke: Float = 2f,
|
||||||
public val color: Color = Color.Red,
|
|
||||||
public val pointMode: PointMode = PointMode.Points,
|
public val pointMode: PointMode = PointMode.Points,
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : Feature<T> {
|
) : Feature<T> {
|
||||||
@ -125,8 +123,6 @@ public data class PointsFeature<T : Any>(
|
|||||||
public data class PolygonFeature<T : Any>(
|
public data class PolygonFeature<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
public val points: List<T>,
|
public val points: List<T>,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
public val color: Color = Color.Red,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : DomainFeature<T> {
|
) : DomainFeature<T> {
|
||||||
|
|
||||||
@ -136,7 +132,8 @@ public data class PolygonFeature<T : Any>(
|
|||||||
|
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T>? = boundingBox
|
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))
|
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>(
|
public data class CircleFeature<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
override val center: T,
|
override val center: T,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
public val size: Dp = 5.dp,
|
public val size: Dp = 5.dp,
|
||||||
public val color: Color = Color.Red,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : MarkerFeature<T> {
|
) : MarkerFeature<T> {
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||||
@ -162,9 +157,7 @@ public data class CircleFeature<T : Any>(
|
|||||||
public data class RectangleFeature<T : Any>(
|
public data class RectangleFeature<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
override val center: T,
|
override val center: T,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
public val size: DpSize = DpSize(5.dp, 5.dp),
|
public val size: DpSize = DpSize(5.dp, 5.dp),
|
||||||
public val color: Color = Color.Red,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : MarkerFeature<T> {
|
) : MarkerFeature<T> {
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||||
@ -180,8 +173,6 @@ public data class LineFeature<T : Any>(
|
|||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
public val a: T,
|
public val a: T,
|
||||||
public val b: T,
|
public val b: T,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
public val color: Color = Color.Red,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : DomainFeature<T> {
|
) : DomainFeature<T> {
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<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 oval: Rectangle<T>,
|
||||||
public val startAngle: Float,
|
public val startAngle: Float,
|
||||||
public val arcLength: Float,
|
public val arcLength: Float,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
public val color: Color = Color.Red,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : DraggableFeature<T> {
|
) : DraggableFeature<T> {
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = oval
|
override fun getBoundingBox(zoom: Float): Rectangle<T> = oval
|
||||||
@ -223,7 +212,6 @@ public data class ArcFeature<T : Any>(
|
|||||||
public data class DrawFeature<T : Any>(
|
public data class DrawFeature<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
public val position: T,
|
public val position: T,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
public val drawFeature: DrawScope.() -> Unit,
|
public val drawFeature: DrawScope.() -> Unit,
|
||||||
) : DraggableFeature<T> {
|
) : DraggableFeature<T> {
|
||||||
@ -240,7 +228,6 @@ public data class BitmapImageFeature<T : Any>(
|
|||||||
override val center: T,
|
override val center: T,
|
||||||
public val size: DpSize,
|
public val size: DpSize,
|
||||||
public val image: ImageBitmap,
|
public val image: ImageBitmap,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : MarkerFeature<T> {
|
) : MarkerFeature<T> {
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
|
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,
|
override val center: T,
|
||||||
public val size: DpSize,
|
public val size: DpSize,
|
||||||
public val image: ImageVector,
|
public val image: ImageVector,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : MarkerFeature<T>, PainterFeature<T> {
|
) : MarkerFeature<T>, PainterFeature<T> {
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
|
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>(
|
public data class ScalableImageFeature<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
public val rectangle: Rectangle<T>,
|
public val rectangle: Rectangle<T>,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
public val painter: @Composable () -> Painter,
|
public val painter: @Composable () -> Painter,
|
||||||
) : Feature<T>, PainterFeature<T> {
|
) : 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>(
|
public data class TextFeature<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
public val position: T,
|
public val position: T,
|
||||||
public val text: String,
|
public val text: String,
|
||||||
override val zoomRange: FloatRange,
|
|
||||||
public val color: Color = Color.Black,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
public val fontConfig: FeatureFont.() -> Unit,
|
public val fontConfig: FeatureFont.() -> Unit,
|
||||||
) : DraggableFeature<T> {
|
) : 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
|
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 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>) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * A group of other features
|
// * Cyclic update of a feature. Called infinitely until canceled.
|
||||||
// */
|
// */
|
||||||
//public data class FeatureGroup<T : Any>(
|
// public fun <F : Feature<T>> FeatureId<F>.updated(
|
||||||
// val parentBuilder: FeatureBuilder<T>,
|
// scope: CoroutineScope,
|
||||||
// private val groupId: String,
|
// update: suspend (F) -> F,
|
||||||
// public override val zoomRange: FloatRange,
|
// ): Job = scope.launch {
|
||||||
// override val attributes: Attributes = Attributes.EMPTY,
|
// while (isActive) {
|
||||||
//) : FeatureBuilder<T>, Feature<T> {
|
// feature(this@updated, update(get(this@updated)))
|
||||||
//
|
|
||||||
// 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)
|
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// override fun getBoundingBox(zoom: Float): Rectangle<T>? {
|
|
||||||
// TODO("Not yet implemented")
|
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> =
|
public fun <F : Feature<T>> FeatureId<F>.color(color: Color): FeatureId<F> =
|
||||||
// copy(attributes = attributes.modify())
|
withAttribute(ColorAttribute, color)
|
||||||
//}
|
public fun <F : Feature<T>> FeatureId<F>.zoomRange(range: FloatRange): FeatureId<F> =
|
||||||
//
|
withAttribute(ZoomRangeAttribute, range)
|
||||||
//public fun <T : Any> FeatureBuilder<T>.group(
|
|
||||||
// zoomRange: FloatRange = defaultZoomRange,
|
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
|
||||||
// id: String? = null,
|
featureMap.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
|
||||||
// builder: FeatureBuilder<T>.() -> Unit,
|
}
|
||||||
//): FeatureId<FeatureGroup<T>> = feature(id, FeatureGroup(this, id ?: generateUID(null), zoomRange).apply(builder))
|
|
||||||
|
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
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
|
|
||||||
|
public fun <T : Any> FeatureGroup<T>.draggableLine(
|
||||||
public fun <T : Any> FeatureCollection<T>.draggableLine(
|
|
||||||
aId: FeatureId<MarkerFeature<T>>,
|
aId: FeatureId<MarkerFeature<T>>,
|
||||||
bId: FeatureId<MarkerFeature<T>>,
|
bId: FeatureId<MarkerFeature<T>>,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
|
||||||
color: Color = Color.Red,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<LineFeature<T>> {
|
): FeatureId<LineFeature<T>> {
|
||||||
var lineId: FeatureId<LineFeature<T>>? = null
|
var lineId: FeatureId<LineFeature<T>>? = null
|
||||||
@ -15,8 +11,6 @@ public fun <T : Any> FeatureCollection<T>.draggableLine(
|
|||||||
fun drawLine(): FeatureId<LineFeature<T>> = line(
|
fun drawLine(): FeatureId<LineFeature<T>> = line(
|
||||||
get(aId).center,
|
get(aId).center,
|
||||||
get(bId).center,
|
get(bId).center,
|
||||||
zoomRange,
|
|
||||||
color,
|
|
||||||
lineId?.id ?: id
|
lineId?.id ?: id
|
||||||
).also {
|
).also {
|
||||||
lineId = it
|
lineId = it
|
||||||
|
@ -14,10 +14,11 @@ import kotlin.math.min
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a modifier for Map/Scheme canvas controls on desktop
|
* 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(
|
public fun <T : Any> Modifier.mapControls(
|
||||||
state: CoordinateViewScope<T>,
|
state: CoordinateViewScope<T>,
|
||||||
features: Map<FeatureId<*>, Feature<T>>,
|
features: Collection<Feature<T>>,
|
||||||
): Modifier = with(state) {
|
): Modifier = with(state) {
|
||||||
pointerInput(Unit) {
|
pointerInput(Unit) {
|
||||||
fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp())
|
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 coordinates = event.changes.first().position.toDpOffset().toCoordinates()
|
||||||
val point = space.ViewPoint(coordinates, zoom)
|
val point = space.ViewPoint(coordinates, zoom)
|
||||||
|
|
||||||
val sortedFeatures =features.values.sortedByDescending { it.z }
|
|
||||||
|
|
||||||
if (event.type == PointerEventType.Move) {
|
if (event.type == PointerEventType.Move) {
|
||||||
for (feature in sortedFeatures) {
|
for (feature in features) {
|
||||||
val listeners = (feature as? DomainFeature)?.attributes?.get(HoverListenerAttribute)
|
val listeners = (feature as? DomainFeature)?.attributes?.get(HoverListenerAttribute)
|
||||||
if (listeners != null && point in feature) {
|
if (listeners != null && point in feature) {
|
||||||
listeners.forEach { it.handle(event, point) }
|
listeners.forEach { it.handle(event, point) }
|
||||||
@ -43,7 +42,7 @@ public fun <T : Any> Modifier.mapControls(
|
|||||||
event,
|
event,
|
||||||
point
|
point
|
||||||
)
|
)
|
||||||
for (feature in sortedFeatures) {
|
for (feature in features) {
|
||||||
val listeners = (feature as? DomainFeature)?.attributes?.get(ClickListenerAttribute)
|
val listeners = (feature as? DomainFeature)?.attributes?.get(ClickListenerAttribute)
|
||||||
if (listeners != null && point in feature) {
|
if (listeners != null && point in feature) {
|
||||||
listeners.forEach { it.handle(event, point) }
|
listeners.forEach { it.handle(event, point) }
|
||||||
@ -97,9 +96,8 @@ public fun <T : Any> Modifier.mapControls(
|
|||||||
val dragResult = config.dragHandle?.handle(event, dragStart, dragEnd)
|
val dragResult = config.dragHandle?.handle(event, dragStart, dragEnd)
|
||||||
if (dragResult?.handleNext == false) return@drag
|
if (dragResult?.handleNext == false) return@drag
|
||||||
|
|
||||||
features.values.asSequence()
|
features.asSequence()
|
||||||
.filterIsInstance<DraggableFeature<T>>()
|
.filterIsInstance<DraggableFeature<T>>()
|
||||||
.sortedByDescending { it.z }
|
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
it.attributes[DraggableAttribute]
|
it.attributes[DraggableAttribute]
|
||||||
}.forEach { handler ->
|
}.forEach { handler ->
|
||||||
|
@ -25,21 +25,21 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
state: CoordinateViewScope<T>,
|
state: CoordinateViewScope<T>,
|
||||||
painterCache: Map<PainterFeature<T>, Painter>,
|
painterCache: Map<PainterFeature<T>, Painter>,
|
||||||
feature: Feature<T>,
|
feature: Feature<T>,
|
||||||
|
|
||||||
): Unit = with(state) {
|
): Unit = with(state) {
|
||||||
|
val color = feature.color ?: Color.Red
|
||||||
val alpha = feature.attributes[AlphaAttribute]?:1f
|
val alpha = feature.attributes[AlphaAttribute]?:1f
|
||||||
fun T.toOffset(): Offset = toOffset(this@drawFeature)
|
fun T.toOffset(): Offset = toOffset(this@drawFeature)
|
||||||
|
|
||||||
when (feature) {
|
when (feature) {
|
||||||
is FeatureSelector -> drawFeature(state, painterCache, feature.selector(state.zoom))
|
is FeatureSelector -> drawFeature(state, painterCache, feature.selector(state.zoom))
|
||||||
is CircleFeature -> drawCircle(
|
is CircleFeature -> drawCircle(
|
||||||
feature.color,
|
color,
|
||||||
feature.size.toPx(),
|
feature.size.toPx(),
|
||||||
center = feature.center.toOffset()
|
center = feature.center.toOffset()
|
||||||
)
|
)
|
||||||
|
|
||||||
is RectangleFeature -> drawRect(
|
is RectangleFeature -> drawRect(
|
||||||
feature.color,
|
color,
|
||||||
topLeft = feature.center.toOffset() - Offset(
|
topLeft = feature.center.toOffset() - Offset(
|
||||||
feature.size.width.toPx() / 2,
|
feature.size.width.toPx() / 2,
|
||||||
feature.size.height.toPx() / 2
|
feature.size.height.toPx() / 2
|
||||||
@ -47,14 +47,14 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
size = feature.size.toSize()
|
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 -> {
|
is ArcFeature -> {
|
||||||
val dpRect = feature.oval.toDpRect().toRect()
|
val dpRect = feature.oval.toDpRect().toRect()
|
||||||
|
|
||||||
val size = Size(dpRect.width, dpRect.height)
|
val size = Size(dpRect.width, dpRect.height)
|
||||||
|
|
||||||
drawArc(
|
drawArc(
|
||||||
color = feature.color,
|
color = color,
|
||||||
startAngle = feature.startAngle / PI.toFloat() * 180f,
|
startAngle = feature.startAngle / PI.toFloat() * 180f,
|
||||||
sweepAngle = feature.arcLength / PI.toFloat() * 180f,
|
sweepAngle = feature.arcLength / PI.toFloat() * 180f,
|
||||||
useCenter = false,
|
useCenter = false,
|
||||||
@ -85,7 +85,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
offset.x + 5,
|
offset.x + 5,
|
||||||
offset.y - 5,
|
offset.y - 5,
|
||||||
Font().apply(feature.fontConfig),
|
Font().apply(feature.fontConfig),
|
||||||
feature.color.toPaint()
|
(feature.color ?: Color.Black).toPaint()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +97,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is FeatureGroup -> {
|
is FeatureGroup -> {
|
||||||
//do nothing
|
feature.featureMap.values.forEach {
|
||||||
feature.children.values.forEach {
|
|
||||||
drawFeature(state, painterCache, it)
|
drawFeature(state, painterCache, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +115,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
val points = feature.points.map { it.toOffset() }
|
val points = feature.points.map { it.toOffset() }
|
||||||
drawPoints(
|
drawPoints(
|
||||||
points = points,
|
points = points,
|
||||||
color = feature.color,
|
color = color,
|
||||||
strokeWidth = feature.stroke,
|
strokeWidth = feature.stroke,
|
||||||
pointMode = feature.pointMode,
|
pointMode = feature.pointMode,
|
||||||
alpha = alpha
|
alpha = alpha
|
||||||
@ -133,7 +132,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
}
|
}
|
||||||
drawPath(
|
drawPath(
|
||||||
path = polygonPath,
|
path = polygonPath,
|
||||||
color = feature.color,
|
color = color,
|
||||||
alpha = alpha
|
alpha = alpha
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,12 @@ import kotlinx.serialization.json.jsonPrimitive
|
|||||||
/**
|
/**
|
||||||
* Add a single Json geometry to a feature builder
|
* Add a single Json geometry to a feature builder
|
||||||
*/
|
*/
|
||||||
public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
public fun FeatureGroup<Gmc>.geoJsonGeometry(
|
||||||
geometry: GeoJsonGeometry,
|
geometry: GeoJsonGeometry,
|
||||||
color: Color = defaultColor,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<Feature<Gmc>> = when (geometry) {
|
): FeatureId<Feature<Gmc>> = when (geometry) {
|
||||||
is GeoJsonLineString -> points(
|
is GeoJsonLineString -> points(
|
||||||
geometry.coordinates,
|
geometry.coordinates,
|
||||||
color = color,
|
|
||||||
pointMode = PointMode.Lines
|
pointMode = PointMode.Lines
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,7 +25,6 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
|||||||
geometry.coordinates.forEach {
|
geometry.coordinates.forEach {
|
||||||
points(
|
points(
|
||||||
it,
|
it,
|
||||||
color = color,
|
|
||||||
pointMode = PointMode.Lines
|
pointMode = PointMode.Lines
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -35,7 +32,6 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
|||||||
|
|
||||||
is GeoJsonMultiPoint -> points(
|
is GeoJsonMultiPoint -> points(
|
||||||
geometry.coordinates,
|
geometry.coordinates,
|
||||||
color = color,
|
|
||||||
pointMode = PointMode.Points
|
pointMode = PointMode.Points
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,15 +39,13 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
|||||||
geometry.coordinates.forEach {
|
geometry.coordinates.forEach {
|
||||||
polygon(
|
polygon(
|
||||||
it.first(),
|
it.first(),
|
||||||
color = color,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is GeoJsonPoint -> circle(geometry.coordinates, color = color, id = id)
|
is GeoJsonPoint -> circle(geometry.coordinates, id = id)
|
||||||
is GeoJsonPolygon -> polygon(
|
is GeoJsonPolygon -> polygon(
|
||||||
geometry.coordinates.first(),
|
geometry.coordinates.first(),
|
||||||
color = color,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
is GeoJsonGeometryCollection -> group(id = id) {
|
is GeoJsonGeometryCollection -> group(id = id) {
|
||||||
@ -63,18 +57,22 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
|||||||
withAttribute(AlphaAttribute, 0.5f)
|
withAttribute(AlphaAttribute, 0.5f)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun FeatureBuilder<Gmc>.geoJsonFeature(
|
public fun FeatureGroup<Gmc>.geoJsonFeature(
|
||||||
geoJson: GeoJsonFeature,
|
geoJson: GeoJsonFeature,
|
||||||
color: Color = defaultColor,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<Feature<Gmc>>? {
|
): FeatureId<Feature<Gmc>>? {
|
||||||
val geometry = geoJson.geometry ?: return null
|
val geometry = geoJson.geometry ?: return null
|
||||||
val idOverride = geoJson.properties?.get("id")?.jsonPrimitive?.contentOrNull ?: id
|
val idOverride = geoJson.properties?.get("id")?.jsonPrimitive?.contentOrNull ?: id
|
||||||
val colorOverride = geoJson.properties?.get("color")?.jsonPrimitive?.intOrNull?.let { Color(it) } ?: color
|
val colorOverride = geoJson.properties?.get("color")?.jsonPrimitive?.intOrNull?.let { Color(it) }
|
||||||
return geoJsonGeometry(geometry, colorOverride, idOverride)
|
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,
|
geoJson: GeoJson,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<Feature<Gmc>>? = when (geoJson) {
|
): FeatureId<Feature<Gmc>>? = when (geoJson) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package center.sciprog.maps.geojson
|
package center.sciprog.maps.geojson
|
||||||
|
|
||||||
import center.sciprog.maps.coordinates.Gmc
|
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.Json
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@ -9,7 +9,7 @@ import java.net.URL
|
|||||||
/**
|
/**
|
||||||
* Add geojson features from url
|
* Add geojson features from url
|
||||||
*/
|
*/
|
||||||
public fun FeatureBuilder<Gmc>.geoJson(
|
public fun FeatureGroup<Gmc>.geoJson(
|
||||||
geoJsonUrl: URL,
|
geoJsonUrl: URL,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
) {
|
) {
|
||||||
|
@ -12,7 +12,7 @@ import center.sciprog.maps.features.*
|
|||||||
|
|
||||||
internal fun Pair<Number, Number>.toCoordinates(): XY = XY(first.toFloat(), second.toFloat())
|
internal fun Pair<Number, Number>.toCoordinates(): XY = XY(first.toFloat(), second.toFloat())
|
||||||
|
|
||||||
fun FeatureBuilder<XY>.background(
|
fun FeatureGroup<XY>.background(
|
||||||
width: Float,
|
width: Float,
|
||||||
height: Float,
|
height: Float,
|
||||||
offset: XY = XY(0f, 0f),
|
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>,
|
centerCoordinates: Pair<Number, Number>,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
zoomRange: FloatRange = defaultZoomRange,
|
||||||
size: Dp = 5.dp,
|
size: Dp = 5.dp,
|
||||||
@ -43,14 +43,14 @@ fun FeatureBuilder<XY>.circle(
|
|||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<CircleFeature<XY>> = circle(centerCoordinates.toCoordinates(), zoomRange, size, color, id = id)
|
): FeatureId<CircleFeature<XY>> = circle(centerCoordinates.toCoordinates(), zoomRange, size, color, id = id)
|
||||||
|
|
||||||
fun FeatureBuilder<XY>.draw(
|
fun FeatureGroup<XY>.draw(
|
||||||
position: Pair<Number, Number>,
|
position: Pair<Number, Number>,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
zoomRange: FloatRange = defaultZoomRange,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
draw: DrawScope.() -> Unit,
|
draw: DrawScope.() -> Unit,
|
||||||
): FeatureId<DrawFeature<XY>> = draw(position.toCoordinates(), zoomRange = zoomRange, id = id, draw = draw)
|
): FeatureId<DrawFeature<XY>> = draw(position.toCoordinates(), zoomRange = zoomRange, id = id, draw = draw)
|
||||||
|
|
||||||
fun FeatureBuilder<XY>.line(
|
fun FeatureGroup<XY>.line(
|
||||||
aCoordinates: Pair<Number, Number>,
|
aCoordinates: Pair<Number, Number>,
|
||||||
bCoordinates: Pair<Number, Number>,
|
bCoordinates: Pair<Number, Number>,
|
||||||
scaleRange: FloatRange = defaultZoomRange,
|
scaleRange: FloatRange = defaultZoomRange,
|
||||||
@ -59,7 +59,7 @@ fun FeatureBuilder<XY>.line(
|
|||||||
): FeatureId<LineFeature<XY>> = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), scaleRange, color, id)
|
): 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>,
|
center: Pair<Double, Double>,
|
||||||
radius: Float,
|
radius: Float,
|
||||||
startAngle: Float,
|
startAngle: Float,
|
||||||
@ -76,7 +76,7 @@ public fun FeatureBuilder<XY>.arc(
|
|||||||
id = id
|
id = id
|
||||||
)
|
)
|
||||||
|
|
||||||
fun FeatureBuilder<XY>.image(
|
fun FeatureGroup<XY>.image(
|
||||||
position: Pair<Number, Number>,
|
position: Pair<Number, Number>,
|
||||||
image: ImageVector,
|
image: ImageVector,
|
||||||
size: DpSize = DpSize(image.defaultWidth, image.defaultHeight),
|
size: DpSize = DpSize(image.defaultWidth, image.defaultHeight),
|
||||||
@ -85,7 +85,7 @@ fun FeatureBuilder<XY>.image(
|
|||||||
): FeatureId<VectorImageFeature<XY>> =
|
): FeatureId<VectorImageFeature<XY>> =
|
||||||
image(position.toCoordinates(), image, size = size, zoomRange = zoomRange, id = id)
|
image(position.toCoordinates(), image, size = size, zoomRange = zoomRange, id = id)
|
||||||
|
|
||||||
fun FeatureBuilder<XY>.text(
|
fun FeatureGroup<XY>.text(
|
||||||
position: Pair<Number, Number>,
|
position: Pair<Number, Number>,
|
||||||
text: String,
|
text: String,
|
||||||
zoomRange: FloatRange = defaultZoomRange,
|
zoomRange: FloatRange = defaultZoomRange,
|
||||||
|
@ -22,12 +22,12 @@ private val logger = KotlinLogging.logger("SchemeView")
|
|||||||
@Composable
|
@Composable
|
||||||
public fun SchemeView(
|
public fun SchemeView(
|
||||||
state: XYViewScope,
|
state: XYViewScope,
|
||||||
featuresState: FeatureCollection<XY>,
|
featuresState: FeatureGroup<XY>,
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
with(state) {
|
with(state) {
|
||||||
val painterCache: Map<PainterFeature<XY>, Painter> = key(featuresState) {
|
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()) {
|
Canvas(modifier = modifier.mapControls(state, featuresState.features).fillMaxSize()) {
|
||||||
@ -38,9 +38,8 @@ public fun SchemeView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
clipRect {
|
clipRect {
|
||||||
featuresState.features.values
|
featuresState.features
|
||||||
.filter { viewPoint.zoom in it.zoomRange }
|
.filter { viewPoint.zoom in it.zoomRange }
|
||||||
.sortedBy { it.z }
|
|
||||||
.forEach { background ->
|
.forEach { background ->
|
||||||
drawFeature(state, painterCache, background)
|
drawFeature(state, painterCache, background)
|
||||||
}
|
}
|
||||||
@ -88,7 +87,7 @@ public fun SchemeView(
|
|||||||
|
|
||||||
|
|
||||||
val featureState = key(featureMap) {
|
val featureState = key(featureMap) {
|
||||||
FeatureCollection.build(XYCoordinateSpace) {
|
FeatureGroup.build(XYCoordinateSpace) {
|
||||||
featureMap.forEach { feature(it.key.id, it.value) }
|
featureMap.forEach { feature(it.key.id, it.value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +95,7 @@ public fun SchemeView(
|
|||||||
val state = rememberMapState(
|
val state = rememberMapState(
|
||||||
config,
|
config,
|
||||||
initialViewPoint = initialViewPoint,
|
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)
|
SchemeView(state, featureState, modifier)
|
||||||
@ -115,13 +114,13 @@ public fun SchemeView(
|
|||||||
initialRectangle: Rectangle<XY>? = null,
|
initialRectangle: Rectangle<XY>? = null,
|
||||||
config: ViewConfig<XY> = ViewConfig(),
|
config: ViewConfig<XY> = ViewConfig(),
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
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(
|
val mapState: XYViewScope = rememberMapState(
|
||||||
config,
|
config,
|
||||||
initialViewPoint = initialViewPoint,
|
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)
|
SchemeView(mapState, featureState, modifier)
|
||||||
|
@ -17,14 +17,14 @@ import kotlin.math.abs
|
|||||||
|
|
||||||
|
|
||||||
class FeatureStateSnapshot<T : Any>(
|
class FeatureStateSnapshot<T : Any>(
|
||||||
val features: Map<FeatureId<*>, Feature<T>>,
|
val features: Map<String, Feature<T>>,
|
||||||
val painterCache: Map<PainterFeature<T>, Painter>,
|
val painterCache: Map<PainterFeature<T>, Painter>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T: Any> FeatureCollection<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
|
fun <T: Any> FeatureGroup<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
|
||||||
features,
|
featureMap,
|
||||||
features.values.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
|
features.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun FeatureStateSnapshot<XY>.generateSvg(
|
fun FeatureStateSnapshot<XY>.generateSvg(
|
||||||
@ -114,7 +114,7 @@ fun FeatureStateSnapshot<XY>.generateSvg(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is FeatureGroup -> {
|
is FeatureGroup -> {
|
||||||
feature.children.values.forEach {
|
feature.featureMap.values.forEach {
|
||||||
drawFeature(scale, it)
|
drawFeature(scale, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user