Add js support for features

This commit is contained in:
Alexander Nozik 2022-12-27 12:34:24 +03:00
parent 8969b2b094
commit 33cadb1d15
13 changed files with 66 additions and 86 deletions

View File

@ -4,5 +4,6 @@ compose.version=1.2.2
agp.version=4.2.2
android.useAndroidX=true
org.jetbrains.compose.experimental.jscanvas.enabled=true
toolsVersion=0.13.3-kotlin-1.7.20

View File

@ -9,36 +9,35 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.coordinates.*
import center.sciprog.maps.features.*
import center.sciprog.maps.features.Feature.Companion.defaultZoomRange
internal fun FeatureCollection<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
internal fun FeatureBuilder<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
GeodeticMapCoordinates.ofDegrees(pair.first.toDouble(), pair.second.toDouble())
public typealias MapFeature = Feature<Gmc>
public fun FeatureCollection<Gmc>.circle(
public fun FeatureBuilder<Gmc>.circle(
centerCoordinates: Pair<Number, Number>,
zoomRange: DoubleRange = defaultZoomRange,
size: Dp = 5.dp,
color: Color = Color.Red,
color: Color = defaultColor,
id: String? = null,
): FeatureId<CircleFeature<Gmc>> = feature(
id, CircleFeature(coordinateSpace, coordinatesOf(centerCoordinates), zoomRange, size, color)
)
public fun FeatureCollection<Gmc>.rectangle(
public fun FeatureBuilder<Gmc>.rectangle(
centerCoordinates: Pair<Number, Number>,
zoomRange: DoubleRange = defaultZoomRange,
size: DpSize = DpSize(5.dp, 5.dp),
color: Color = Color.Red,
color: Color = defaultColor,
id: String? = null,
): FeatureId<RectangleFeature<Gmc>> = feature(
id, RectangleFeature(coordinateSpace, coordinatesOf(centerCoordinates), zoomRange, size, color)
)
public fun FeatureCollection<Gmc>.draw(
public fun FeatureBuilder<Gmc>.draw(
position: Pair<Number, Number>,
zoomRange: DoubleRange = defaultZoomRange,
id: String? = null,
@ -49,10 +48,10 @@ public fun FeatureCollection<Gmc>.draw(
)
public fun FeatureCollection<Gmc>.line(
public fun FeatureBuilder<Gmc>.line(
curve: GmcCurve,
zoomRange: DoubleRange = defaultZoomRange,
color: Color = Color.Red,
color: Color = defaultColor,
id: String? = null,
): FeatureId<LineFeature<Gmc>> = feature(
id,
@ -60,11 +59,11 @@ public fun FeatureCollection<Gmc>.line(
)
public fun FeatureCollection<Gmc>.line(
public fun FeatureBuilder<Gmc>.line(
aCoordinates: Pair<Double, Double>,
bCoordinates: Pair<Double, Double>,
zoomRange: DoubleRange = defaultZoomRange,
color: Color = Color.Red,
color: Color = defaultColor,
id: String? = null,
): FeatureId<LineFeature<Gmc>> = feature(
id,
@ -72,19 +71,19 @@ public fun FeatureCollection<Gmc>.line(
)
public fun FeatureCollection<Gmc>.arc(
public fun FeatureBuilder<Gmc>.arc(
center: Pair<Double, Double>,
radius: Distance,
startAngle: Angle,
arcLength: Angle,
zoomRange: DoubleRange = defaultZoomRange,
color: Color = Color.Red,
color: Color = defaultColor,
id: String? = null,
): FeatureId<ArcFeature<Gmc>> = feature(
id,
ArcFeature(
coordinateSpace,
oval = Rectangle(coordinatesOf(center), radius, radius),
oval = coordinateSpace.Rectangle(coordinatesOf(center), radius, radius),
startAngle = startAngle.radians.toFloat(),
arcLength = arcLength.radians.toFloat(),
zoomRange = zoomRange,
@ -92,17 +91,17 @@ public fun FeatureCollection<Gmc>.arc(
)
)
public fun FeatureCollection<Gmc>.points(
public fun FeatureBuilder<Gmc>.points(
points: List<Pair<Double, Double>>,
zoomRange: DoubleRange = defaultZoomRange,
stroke: Float = 2f,
color: Color = Color.Red,
color: Color = defaultColor,
pointMode: PointMode = PointMode.Points,
id: String? = null,
): FeatureId<PointsFeature<Gmc>> =
feature(id, PointsFeature(coordinateSpace, points.map(::coordinatesOf), zoomRange, stroke, color, pointMode))
public fun FeatureCollection<Gmc>.image(
public fun FeatureBuilder<Gmc>.image(
position: Pair<Double, Double>,
image: ImageVector,
size: DpSize = DpSize(20.dp, 20.dp),
@ -119,11 +118,11 @@ public fun FeatureCollection<Gmc>.image(
)
)
public fun FeatureCollection<Gmc>.text(
public fun FeatureBuilder<Gmc>.text(
position: Pair<Double, Double>,
text: String,
zoomRange: DoubleRange = defaultZoomRange,
color: Color = Color.Red,
color: Color = defaultColor,
font: FeatureFont.() -> Unit = { size = 16f },
id: String? = null,
): FeatureId<TextFeature<Gmc>> = feature(

View File

@ -98,7 +98,7 @@ public actual fun MapView(
}
val painterCache: Map<PainterFeature<Gmc>, Painter> = key(featuresState) {
featuresState.features.values.filterIsInstance<PainterFeature<Gmc>>().associateWith { it.painter() }
featuresState.features.values.filterIsInstance<PainterFeature<Gmc>>().associateWith { it.getPainter() }
}
Canvas(modifier = modifier.mapControls(state).fillMaxSize()) {

View File

@ -6,6 +6,7 @@ package center.sciprog.maps.coordinates
public class GeodeticMapCoordinates(
public val latitude: Angle,
longitude: Angle,
public val elevation: Distance = 0.kilometers
) {
public val longitude: Angle = longitude.normalized(Angle.zero)

View File

@ -1,39 +1,15 @@
plugins {
kotlin("multiplatform")
id("space.kscience.gradle.mpp")
id("org.jetbrains.compose")
`maven-publish`
}
kotlin {
explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Warning
jvm {
compilations.all {
kotlinOptions.jvmTarget = space.kscience.gradle.KScienceVersions.JVM_TARGET.toString()
}
}
sourceSets {
commonMain {
dependencies {
api(compose.foundation)
}
}
val jvmMain by getting{
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test-junit5"))
implementation("org.junit.jupiter:junit-jupiter:5.8.2")
}
}
}
}
java {
targetCompatibility = space.kscience.gradle.KScienceVersions.JVM_TARGET
}
tasks.withType<Test> {
useJUnitPlatform()
}

View File

@ -30,7 +30,7 @@ public class AttributeMap {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
if (other == null || this::class != other::class) return false
other as AttributeMap

View File

@ -13,7 +13,6 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.features.Feature.Companion.defaultZoomRange
public typealias DoubleRange = FloatRange
public typealias FloatRange = ClosedFloatingPointRange<Float>
@ -28,15 +27,11 @@ public interface Feature<T : Any> {
public var attributes: AttributeMap
public fun getBoundingBox(zoom: Float): Rectangle<T>?
public companion object {
public val defaultZoomRange: FloatRange = 0f..Float.POSITIVE_INFINITY
}
}
public interface PainterFeature<T:Any>: Feature<T> {
@Composable
public fun painter(): Painter
public fun getPainter(): Painter
}
public interface SelectableFeature<T : Any> : Feature<T> {
@ -72,10 +67,11 @@ public fun <T : Any> Iterable<Feature<T>>.computeBoundingBox(
*/
public class FeatureSelector<T : Any>(
override val space: CoordinateSpace<T>,
override val zoomRange: FloatRange,
override var attributes: AttributeMap = AttributeMap(),
public val selector: (zoom: Float) -> Feature<T>,
) : Feature<T> {
override val zoomRange: FloatRange get() = defaultZoomRange
override fun getBoundingBox(zoom: Float): Rectangle<T>? = selector(zoom).getBoundingBox(zoom)
}
@ -85,9 +81,9 @@ public 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 zoomRange: FloatRange = defaultZoomRange,
override var attributes: AttributeMap = AttributeMap(),
) : DraggableFeature<T> {
override fun withCoordinates(newCoordinates: T): Feature<T> = with(space) {
@ -109,7 +105,7 @@ public class PathFeature<T : Any>(
public class PointsFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val points: List<T>,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
public val stroke: Float = 2f,
public val color: Color = Color.Red,
public val pointMode: PointMode = PointMode.Points,
@ -123,7 +119,7 @@ public class PointsFeature<T : Any>(
public data class CircleFeature<T : Any>(
override val space: CoordinateSpace<T>,
override val center: T,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
public val size: Dp = 5.dp,
public val color: Color = Color.Red,
override var attributes: AttributeMap = AttributeMap(),
@ -138,7 +134,7 @@ public data class CircleFeature<T : Any>(
public class RectangleFeature<T : Any>(
override val space: CoordinateSpace<T>,
override val center: T,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
public val size: DpSize = DpSize(5.dp, 5.dp),
public val color: Color = Color.Red,
override var attributes: AttributeMap = AttributeMap(),
@ -154,7 +150,7 @@ public class LineFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val a: T,
public val b: T,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
public val color: Color = Color.Red,
override var attributes: AttributeMap = AttributeMap(),
) : SelectableFeature<T> {
@ -175,7 +171,7 @@ public class ArcFeature<T : Any>(
public val oval: Rectangle<T>,
public val startAngle: Float,
public val arcLength: Float,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
public val color: Color = Color.Red,
override var attributes: AttributeMap = AttributeMap(),
) : DraggableFeature<T> {
@ -189,7 +185,7 @@ public class ArcFeature<T : Any>(
public data class DrawFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val position: T,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
override var attributes: AttributeMap = AttributeMap(),
public val drawFeature: DrawScope.() -> Unit,
) : DraggableFeature<T> {
@ -203,7 +199,7 @@ public data class BitmapImageFeature<T : Any>(
override val center: T,
public val size: DpSize,
public val image: ImageBitmap,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
override var attributes: AttributeMap = AttributeMap(),
) : MarkerFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
@ -216,7 +212,7 @@ public data class VectorImageFeature<T : Any>(
override val center: T,
public val size: DpSize,
public val image: ImageVector,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
override var attributes: AttributeMap = AttributeMap(),
) : MarkerFeature<T>, PainterFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
@ -224,7 +220,7 @@ public data class VectorImageFeature<T : Any>(
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
@Composable
override fun painter(): VectorPainter = rememberVectorPainter(image)
override fun getPainter(): VectorPainter = rememberVectorPainter(image)
}
/**
@ -235,12 +231,12 @@ public data class VectorImageFeature<T : Any>(
public class ScalableImageFeature<T: Any>(
override val space: CoordinateSpace<T>,
public val rectangle: Rectangle<T>,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
override var attributes: AttributeMap = AttributeMap(),
public val painter: @Composable () -> Painter,
) : Feature<T>, PainterFeature<T>{
@Composable
override fun painter(): Painter = painter.invoke()
override fun getPainter(): Painter = painter.invoke()
override fun getBoundingBox(zoom: Float): Rectangle<T> =rectangle
}
@ -252,7 +248,7 @@ public class ScalableImageFeature<T: Any>(
public class FeatureGroup<T : Any>(
override val space: CoordinateSpace<T>,
public val children: Map<FeatureId<*>, Feature<T>>,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
override var attributes: AttributeMap = AttributeMap(),
) : Feature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
@ -264,7 +260,7 @@ public class TextFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val position: T,
public val text: String,
override val zoomRange: FloatRange = defaultZoomRange,
override val zoomRange: FloatRange,
public val color: Color = Color.Black,
override var attributes: AttributeMap = AttributeMap(),
public val fontConfig: FeatureFont.() -> Unit,

View File

@ -11,11 +11,11 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.features.Feature.Companion.defaultZoomRange
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)
@ -29,6 +29,8 @@ public interface FeatureBuilder<T : Any> {
public fun <F : Feature<T>, V> setAttribute(id: FeatureId<F>, key: Feature.Attribute<V>, value: V?)
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> =

View File

@ -6,7 +6,7 @@ import androidx.compose.ui.graphics.Color
public fun <T : Any> FeatureCollection<T>.draggableLine(
aId: FeatureId<MarkerFeature<T>>,
bId: FeatureId<MarkerFeature<T>>,
zoomRange: FloatRange = Feature.defaultZoomRange,
zoomRange: FloatRange = defaultZoomRange,
color: Color = Color.Red,
id: String? = null,
): FeatureId<LineFeature<T>> {

View File

@ -0,0 +1,5 @@
package center.sciprog.maps.features
public actual class FeatureFont {
public actual var size: Float = 16f
}

View File

@ -12,7 +12,7 @@ import center.sciprog.maps.features.*
internal fun Pair<Number, Number>.toCoordinates(): XY = XY(first.toFloat(), second.toFloat())
fun FeatureCollection<XY>.background(
fun FeatureBuilder<XY>.background(
width: Float,
height: Float,
offset: XY = XY(0f, 0f),
@ -25,42 +25,42 @@ fun FeatureCollection<XY>.background(
)
return feature(
id,
ScalableImageFeature(coordinateSpace, box, painter = painter).apply {
ScalableImageFeature(coordinateSpace, box, zoomRange = defaultZoomRange, painter = painter).apply {
z = -100f
}
)
}
fun FeatureCollection<XY>.circle(
fun FeatureBuilder<XY>.circle(
centerCoordinates: Pair<Number, Number>,
zoomRange: FloatRange = Feature.defaultZoomRange,
zoomRange: FloatRange = defaultZoomRange,
size: Dp = 5.dp,
color: Color = Color.Red,
id: String? = null,
): FeatureId<CircleFeature<XY>> = circle(centerCoordinates.toCoordinates(), zoomRange, size, color, id = id)
fun FeatureCollection<XY>.draw(
fun FeatureBuilder<XY>.draw(
position: Pair<Number, Number>,
zoomRange: FloatRange = Feature.defaultZoomRange,
zoomRange: FloatRange = defaultZoomRange,
id: String? = null,
draw: DrawScope.() -> Unit,
): FeatureId<DrawFeature<XY>> = draw(position.toCoordinates(), zoomRange = zoomRange, id = id, draw = draw)
fun FeatureCollection<XY>.line(
fun FeatureBuilder<XY>.line(
aCoordinates: Pair<Number, Number>,
bCoordinates: Pair<Number, Number>,
scaleRange: FloatRange = Feature.defaultZoomRange,
scaleRange: FloatRange = defaultZoomRange,
color: Color = Color.Red,
id: String? = null,
): FeatureId<LineFeature<XY>> = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), scaleRange, color, id)
public fun FeatureCollection<XY>.arc(
public fun FeatureBuilder<XY>.arc(
center: Pair<Double, Double>,
radius: Float,
startAngle: Float,
arcLength: Float,
zoomRange: FloatRange = Feature.defaultZoomRange,
zoomRange: FloatRange = defaultZoomRange,
color: Color = Color.Red,
id: String? = null,
): FeatureId<ArcFeature<XY>> = arc(
@ -71,19 +71,19 @@ public fun FeatureCollection<XY>.arc(
color = color
)
fun FeatureCollection<XY>.image(
fun FeatureBuilder<XY>.image(
position: Pair<Number, Number>,
image: ImageVector,
size: DpSize = DpSize(image.defaultWidth, image.defaultHeight),
zoomRange: FloatRange = Feature.defaultZoomRange,
zoomRange: FloatRange = defaultZoomRange,
id: String? = null,
): FeatureId<VectorImageFeature<XY>> =
image(position.toCoordinates(), image, size = size, zoomRange = zoomRange, id = id)
fun FeatureCollection<XY>.text(
fun FeatureBuilder<XY>.text(
position: Pair<Number, Number>,
text: String,
zoomRange: FloatRange = Feature.defaultZoomRange,
zoomRange: FloatRange = defaultZoomRange,
color: Color = Color.Red,
id: String? = null,
): FeatureId<TextFeature<XY>> = text(position.toCoordinates(), text, zoomRange, color, id = id)

View File

@ -37,7 +37,7 @@ public fun SchemeView(
}
with(state) {
val painterCache: Map<PainterFeature<XY>, Painter> = key(featuresState) {
featuresState.features.values.filterIsInstance<PainterFeature<XY>>().associateWith { it.painter() }
featuresState.features.values.filterIsInstance<PainterFeature<XY>>().associateWith { it.getPainter() }
}
Canvas(modifier = modifier.mapControls(state).fillMaxSize()) {

View File

@ -24,7 +24,7 @@ class FeatureStateSnapshot<T : Any>(
@Composable
fun <T: Any> FeatureCollection<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
features,
features.values.filterIsInstance<PainterFeature<T>>().associateWith { it.painter() }
features.values.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
)
fun FeatureStateSnapshot<XY>.generateSvg(