Immutable attributes
This commit is contained in:
parent
7643968d39
commit
26c3e589da
@ -109,6 +109,15 @@ fun App() {
|
||||
text(position = it, it.toShortString(), id = "text", color = Color.Blue)
|
||||
}
|
||||
}.launchIn(scope)
|
||||
|
||||
features.forEach { (id, feature) ->
|
||||
if (feature is PolygonFeature) {
|
||||
(id as FeatureId<PolygonFeature<Gmc>>).onHover {
|
||||
println("Hover on $id")
|
||||
points(feature.points, color = Color.Blue, id = "selected", attributes = Attributes(ZAttribute, 10f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import center.sciprog.maps.coordinates.*
|
||||
import center.sciprog.maps.features.CoordinateSpace
|
||||
import center.sciprog.maps.features.Rectangle
|
||||
import center.sciprog.maps.features.ViewPoint
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.pow
|
||||
|
||||
@ -82,6 +83,13 @@ public object WebMercatorSpace : CoordinateSpace<Gmc> {
|
||||
(mercatorA.y - mercatorB.y).dp * tileScale
|
||||
)
|
||||
}
|
||||
|
||||
override fun Gmc.isInsidePolygon(points: List<Gmc>): Boolean = points.zipWithNext().count { (left, right) ->
|
||||
val dist = right.latitude - left.latitude
|
||||
val intersection = left.latitude * abs((right.longitude - longitude) / dist) +
|
||||
right.latitude * abs((longitude - left.longitude) / dist)
|
||||
longitude in left.longitude..right.longitude && intersection >= latitude
|
||||
} % 2 == 0
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,56 +18,56 @@ public typealias MapFeature = Feature<Gmc>
|
||||
|
||||
public fun FeatureBuilder<Gmc>.circle(
|
||||
centerCoordinates: Pair<Number, Number>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: Dp = 5.dp,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<CircleFeature<Gmc>> = feature(
|
||||
id, CircleFeature(coordinateSpace, coordinatesOf(centerCoordinates), zoomRange, size, color)
|
||||
id, CircleFeature(space, coordinatesOf(centerCoordinates), zoomRange, size, color)
|
||||
)
|
||||
|
||||
public fun FeatureBuilder<Gmc>.rectangle(
|
||||
centerCoordinates: Pair<Number, Number>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
size: DpSize = DpSize(5.dp, 5.dp),
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<RectangleFeature<Gmc>> = feature(
|
||||
id, RectangleFeature(coordinateSpace, coordinatesOf(centerCoordinates), zoomRange, size, color)
|
||||
id, RectangleFeature(space, coordinatesOf(centerCoordinates), zoomRange, size, color)
|
||||
)
|
||||
|
||||
|
||||
public fun FeatureBuilder<Gmc>.draw(
|
||||
position: Pair<Number, Number>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
draw: DrawScope.() -> Unit,
|
||||
): FeatureId<DrawFeature<Gmc>> = feature(
|
||||
id,
|
||||
DrawFeature(coordinateSpace, coordinatesOf(position), zoomRange, drawFeature = draw)
|
||||
DrawFeature(space, coordinatesOf(position), zoomRange, drawFeature = draw)
|
||||
)
|
||||
|
||||
|
||||
public fun FeatureBuilder<Gmc>.line(
|
||||
curve: GmcCurve,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<LineFeature<Gmc>> = feature(
|
||||
id,
|
||||
LineFeature(coordinateSpace, curve.forward.coordinates, curve.backward.coordinates, zoomRange, color)
|
||||
LineFeature(space, curve.forward.coordinates, curve.backward.coordinates, zoomRange, color)
|
||||
)
|
||||
|
||||
|
||||
public fun FeatureBuilder<Gmc>.line(
|
||||
aCoordinates: Pair<Double, Double>,
|
||||
bCoordinates: Pair<Double, Double>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<LineFeature<Gmc>> = feature(
|
||||
id,
|
||||
LineFeature(coordinateSpace, coordinatesOf(aCoordinates), coordinatesOf(bCoordinates), zoomRange, color)
|
||||
LineFeature(space, coordinatesOf(aCoordinates), coordinatesOf(bCoordinates), zoomRange, color)
|
||||
)
|
||||
|
||||
|
||||
@ -76,14 +76,14 @@ public fun FeatureBuilder<Gmc>.arc(
|
||||
radius: Distance,
|
||||
startAngle: Angle,
|
||||
arcLength: Angle,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<ArcFeature<Gmc>> = feature(
|
||||
id,
|
||||
ArcFeature(
|
||||
coordinateSpace,
|
||||
oval = coordinateSpace.Rectangle(coordinatesOf(center), radius, radius),
|
||||
space,
|
||||
oval = space.Rectangle(coordinatesOf(center), radius, radius),
|
||||
startAngle = startAngle.radians.toFloat(),
|
||||
arcLength = arcLength.radians.toFloat(),
|
||||
zoomRange = zoomRange,
|
||||
@ -93,24 +93,24 @@ public fun FeatureBuilder<Gmc>.arc(
|
||||
|
||||
public fun FeatureBuilder<Gmc>.points(
|
||||
points: List<Pair<Double, Double>>,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
stroke: Float = 2f,
|
||||
color: Color = defaultColor,
|
||||
pointMode: PointMode = PointMode.Points,
|
||||
id: String? = null,
|
||||
): FeatureId<PointsFeature<Gmc>> =
|
||||
feature(id, PointsFeature(coordinateSpace, points.map(::coordinatesOf), zoomRange, stroke, color, pointMode))
|
||||
feature(id, PointsFeature(space, points.map(::coordinatesOf), zoomRange, stroke, color, pointMode))
|
||||
|
||||
public fun FeatureBuilder<Gmc>.image(
|
||||
position: Pair<Double, Double>,
|
||||
image: ImageVector,
|
||||
size: DpSize = DpSize(20.dp, 20.dp),
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
id: String? = null,
|
||||
): FeatureId<VectorImageFeature<Gmc>> = feature(
|
||||
id,
|
||||
VectorImageFeature(
|
||||
coordinateSpace,
|
||||
space,
|
||||
coordinatesOf(position),
|
||||
size,
|
||||
image,
|
||||
@ -121,11 +121,11 @@ public fun FeatureBuilder<Gmc>.image(
|
||||
public fun FeatureBuilder<Gmc>.text(
|
||||
position: Pair<Double, Double>,
|
||||
text: String,
|
||||
zoomRange: DoubleRange = defaultZoomRange,
|
||||
zoomRange: FloatRange = defaultZoomRange,
|
||||
color: Color = defaultColor,
|
||||
font: FeatureFont.() -> Unit = { size = 16f },
|
||||
id: String? = null,
|
||||
): FeatureId<TextFeature<Gmc>> = feature(
|
||||
id,
|
||||
TextFeature(coordinateSpace, coordinatesOf(position), text, zoomRange, color, fontConfig = font)
|
||||
TextFeature(space, coordinatesOf(position), text, zoomRange, color, fontConfig = font)
|
||||
)
|
||||
|
@ -0,0 +1,21 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
public interface Attribute<T>
|
||||
|
||||
public object ZAttribute : Attribute<Float>
|
||||
|
||||
public object DraggableAttribute : Attribute<DragHandle<Any>>
|
||||
|
||||
public object DragListenerAttribute : Attribute<Set<DragListener<Any>>>
|
||||
|
||||
public object ClickListenerAttribute : Attribute<Set<MouseListener<Any>>>
|
||||
|
||||
public object HoverListenerAttribute : Attribute<Set<MouseListener<Any>>>
|
||||
|
||||
public object VisibleAttribute : Attribute<Boolean>
|
||||
|
||||
public object ColorAttribute : Attribute<Color>
|
||||
|
||||
public object AlphaAttribute : Attribute<Float>
|
@ -1,55 +0,0 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
public object ZAttribute : Feature.Attribute<Float>
|
||||
|
||||
public object DraggableAttribute : Feature.Attribute<DragHandle<Any>>
|
||||
|
||||
public object DragListenerAttribute : Feature.Attribute<Set<DragListener<Any>>>
|
||||
|
||||
public object ClickableListenerAttribute : Feature.Attribute<Set<ClickListener<Any>>>
|
||||
|
||||
public object VisibleAttribute : Feature.Attribute<Boolean>
|
||||
|
||||
public object ColorAttribute : Feature.Attribute<Color>
|
||||
|
||||
public class AttributeMap {
|
||||
public val map: MutableMap<Feature.Attribute<*>, Any> = mutableStateMapOf()
|
||||
|
||||
public operator fun <T, A : Feature.Attribute<T>> set(
|
||||
attribute: A,
|
||||
attrValue: T?,
|
||||
) {
|
||||
if (attrValue == null) {
|
||||
map.remove(attribute)
|
||||
} else {
|
||||
map[attribute] = attrValue
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public operator fun <T> get(attribute: Feature.Attribute<T>): T? = map[attribute] as? T
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
|
||||
other as AttributeMap
|
||||
|
||||
if (map != other.map) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = map.hashCode()
|
||||
|
||||
override fun toString(): String = "AttributeMap(value=${map.entries})"
|
||||
}
|
||||
|
||||
public var Feature<*>.z: Float
|
||||
get() = attributes[ZAttribute] ?: 0f
|
||||
set(value) {
|
||||
attributes[ZAttribute] = value
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
@Stable
|
||||
@JvmInline
|
||||
public value class Attributes internal constructor(internal val map: Map<Attribute<*>, Any>) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public operator fun <T> get(attribute: Attribute<T>): T? = map[attribute] as? T
|
||||
|
||||
public fun <T> Attribute<T>.invoke(value: T?): Attributes = withAttribute(this, value)
|
||||
|
||||
override fun toString(): String = "AttributeMap(value=${map.entries})"
|
||||
|
||||
public companion object {
|
||||
public val EMPTY: Attributes = Attributes(emptyMap())
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T, A : Attribute<T>> Attributes.withAttribute(
|
||||
attribute: A,
|
||||
attrValue: T?,
|
||||
): Attributes = Attributes(
|
||||
if (attrValue == null) {
|
||||
map - attribute
|
||||
} else {
|
||||
map + (attribute to attrValue)
|
||||
}
|
||||
)
|
||||
|
||||
public fun <T : Any, A : Attribute<T>> Attributes(
|
||||
attribute: A,
|
||||
attrValue: T,
|
||||
): Attributes = Attributes(mapOf(attribute to attrValue))
|
||||
|
||||
public operator fun Attributes.plus(other: Attributes): Attributes = Attributes(map + other.map)
|
||||
|
||||
public val Feature<*>.z: Float
|
||||
get() = attributes[ZAttribute] ?: 0f
|
||||
// set(value) {
|
||||
// attributes[ZAttribute] = value
|
||||
// }
|
@ -76,6 +76,8 @@ public interface CoordinateSpace<T : Any> {
|
||||
|
||||
return distanceVale.dp
|
||||
}
|
||||
|
||||
public fun T.isInsidePolygon(points: List<T>): Boolean
|
||||
}
|
||||
|
||||
public fun <T : Any> CoordinateSpace<T>.Rectangle(viewPoint: ViewPoint<T>, size: DpSize): Rectangle<T> =
|
||||
|
@ -1,6 +1,7 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
@ -14,19 +15,19 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
public typealias DoubleRange = FloatRange
|
||||
public typealias FloatRange = ClosedFloatingPointRange<Float>
|
||||
|
||||
public interface Feature<T : Any> {
|
||||
public interface Attribute<T>
|
||||
|
||||
public val space: CoordinateSpace<T>
|
||||
|
||||
public val zoomRange: FloatRange
|
||||
|
||||
public val attributes: AttributeMap
|
||||
public val attributes: Attributes
|
||||
|
||||
public fun getBoundingBox(zoom: Float): Rectangle<T>?
|
||||
|
||||
public fun withAttributes(modify: Attributes.() -> Attributes): Feature<T>
|
||||
}
|
||||
|
||||
public interface PainterFeature<T : Any> : Feature<T> {
|
||||
@ -34,13 +35,13 @@ public interface PainterFeature<T : Any> : Feature<T> {
|
||||
public fun getPainter(): Painter
|
||||
}
|
||||
|
||||
public interface ClickableFeature<T : Any> : Feature<T> {
|
||||
public interface DomainFeature<T : Any> : Feature<T> {
|
||||
public operator fun contains(viewPoint: ViewPoint<T>): Boolean = getBoundingBox(viewPoint.zoom)?.let {
|
||||
viewPoint.focus in it
|
||||
} ?: false
|
||||
}
|
||||
|
||||
public interface DraggableFeature<T : Any> : ClickableFeature<T> {
|
||||
public interface DraggableFeature<T : Any> : DomainFeature<T> {
|
||||
public fun withCoordinates(newCoordinates: T): Feature<T>
|
||||
}
|
||||
|
||||
@ -58,24 +59,24 @@ public fun <T : Any> Iterable<Feature<T>>.computeBoundingBox(
|
||||
mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
|
||||
}
|
||||
|
||||
//public fun Pair<Number, Number>.toCoordinates(): GeodeticMapCoordinates =
|
||||
// GeodeticMapCoordinates.ofDegrees(first.toDouble(), second.toDouble())
|
||||
|
||||
|
||||
/**
|
||||
* A feature that decides what to show depending on the zoom value (it could change size of shape)
|
||||
*/
|
||||
public class FeatureSelector<T : Any>(
|
||||
@Stable
|
||||
public data class FeatureSelector<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
public val selector: (zoom: Float) -> Feature<T>,
|
||||
) : Feature<T> {
|
||||
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T>? = selector(zoom).getBoundingBox(zoom)
|
||||
|
||||
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
public class PathFeature<T : Any>(
|
||||
@Stable
|
||||
public data class PathFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val rectangle: Rectangle<T>,
|
||||
public val path: Path,
|
||||
@ -83,7 +84,7 @@ public class PathFeature<T : Any>(
|
||||
override val zoomRange: FloatRange,
|
||||
public val style: DrawStyle = Fill,
|
||||
public val targetRect: Rect = path.getBounds(),
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : DraggableFeature<T> {
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = with(space) {
|
||||
PathFeature(
|
||||
@ -98,126 +99,172 @@ public class PathFeature<T : Any>(
|
||||
}
|
||||
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = rectangle
|
||||
|
||||
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
public class PointsFeature<T : Any>(
|
||||
@Stable
|
||||
public data class PointsFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val points: List<T>,
|
||||
override val zoomRange: FloatRange,
|
||||
public val stroke: Float = 2f,
|
||||
public val color: Color = Color.Red,
|
||||
public val pointMode: PointMode = PointMode.Points,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : Feature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
|
||||
points.wrapPoints()
|
||||
|
||||
private val boundingBox by lazy {
|
||||
with(space) { points.wrapPoints() }
|
||||
}
|
||||
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T>? = boundingBox
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
@Stable
|
||||
public data class PolygonFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val points: List<T>,
|
||||
override val zoomRange: FloatRange,
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : DomainFeature<T> {
|
||||
|
||||
private val boundingBox: Rectangle<T>? by lazy {
|
||||
with(space) { points.wrapPoints() }
|
||||
}
|
||||
|
||||
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 withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
@Stable
|
||||
public data class CircleFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
override val center: T,
|
||||
override val zoomRange: FloatRange,
|
||||
public val size: Dp = 5.dp,
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : MarkerFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||
space.Rectangle(center, zoom, DpSize(size, size))
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||
CircleFeature(space, newCoordinates, zoomRange, size, color, attributes)
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
public class RectangleFeature<T : Any>(
|
||||
@Stable
|
||||
public data class RectangleFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
override val center: T,
|
||||
override val zoomRange: FloatRange,
|
||||
public val size: DpSize = DpSize(5.dp, 5.dp),
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : MarkerFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||
space.Rectangle(center, zoom, size)
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||
RectangleFeature(space, newCoordinates, zoomRange, size, color, attributes)
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
public class LineFeature<T : Any>(
|
||||
@Stable
|
||||
public data class LineFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val a: T,
|
||||
public val b: T,
|
||||
override val zoomRange: FloatRange,
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
) : ClickableFeature<T> {
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : DomainFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||
space.Rectangle(a, b)
|
||||
|
||||
override fun contains(viewPoint: ViewPoint<T>): Boolean = with(space) {
|
||||
viewPoint.focus in space.Rectangle(a, b) && viewPoint.focus.distanceToLine(a, b, viewPoint.zoom).value < 5f
|
||||
viewPoint.focus in getBoundingBox(viewPoint.zoom) && viewPoint.focus.distanceToLine(
|
||||
a,
|
||||
b,
|
||||
viewPoint.zoom
|
||||
).value < 5f
|
||||
}
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param startAngle the angle from 3 o'clock downwards for the start of the arc in radians
|
||||
* @param arcLength arc length in radians
|
||||
*/
|
||||
public class ArcFeature<T : Any>(
|
||||
@Stable
|
||||
public data class ArcFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val oval: Rectangle<T>,
|
||||
public val startAngle: Float,
|
||||
public val arcLength: Float,
|
||||
override val zoomRange: FloatRange,
|
||||
public val color: Color = Color.Red,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : DraggableFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = oval
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = with(space) {
|
||||
ArcFeature(space, oval.withCenter(newCoordinates), startAngle, arcLength, zoomRange, color, attributes)
|
||||
}
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||
copy(oval = with(space) { oval.withCenter(newCoordinates) })
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
public data class DrawFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val position: T,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
public val drawFeature: DrawScope.() -> Unit,
|
||||
) : DraggableFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(position, position)
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(position = newCoordinates)
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
@Stable
|
||||
public data class BitmapImageFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
override val center: T,
|
||||
public val size: DpSize,
|
||||
public val image: ImageBitmap,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : MarkerFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
@Stable
|
||||
public data class VectorImageFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
override val center: T,
|
||||
public val size: DpSize,
|
||||
public val image: ImageVector,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
) : MarkerFeature<T>, PainterFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
|
||||
@Composable
|
||||
override fun getPainter(): VectorPainter = rememberVectorPainter(image)
|
||||
}
|
||||
@ -227,45 +274,50 @@ public data class VectorImageFeature<T : Any>(
|
||||
*
|
||||
* @param rectangle the size of background in scheme size units. The screen units to scheme units ratio equals scale.
|
||||
*/
|
||||
public class ScalableImageFeature<T : Any>(
|
||||
public data class ScalableImageFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val rectangle: Rectangle<T>,
|
||||
override val zoomRange: FloatRange,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
public val painter: @Composable () -> Painter,
|
||||
) : Feature<T>, PainterFeature<T> {
|
||||
@Composable
|
||||
override fun getPainter(): Painter = painter.invoke()
|
||||
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = rectangle
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A group of other features
|
||||
*/
|
||||
public class FeatureGroup<T : Any>(
|
||||
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: AttributeMap = AttributeMap(),
|
||||
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 class TextFeature<T : Any>(
|
||||
public data class TextFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val position: T,
|
||||
public val text: String,
|
||||
override val zoomRange: FloatRange,
|
||||
public val color: Color = Color.Black,
|
||||
override val attributes: AttributeMap = AttributeMap(),
|
||||
override val attributes: Attributes = Attributes.EMPTY,
|
||||
public val fontConfig: FeatureFont.() -> Unit,
|
||||
) : DraggableFeature<T> {
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(position, position)
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||
TextFeature(space, newCoordinates, text, zoomRange, color, attributes, fontConfig)
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(position = newCoordinates)
|
||||
|
||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ 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
|
||||
@ -23,11 +24,13 @@ public value class FeatureId<out F : Feature<*>>(public val id: String)
|
||||
|
||||
public interface FeatureBuilder<T : Any> {
|
||||
|
||||
public val coordinateSpace: CoordinateSpace<T>
|
||||
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> setAttribute(id: FeatureId<F>, key: Feature.Attribute<V>, value: V?)
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.withAttribute(key: Attribute<V>, value: V?): FeatureId<F>
|
||||
|
||||
public val defaultColor: Color get() = Color.Red
|
||||
|
||||
@ -38,11 +41,11 @@ public fun <T : Any, F : Feature<T>> FeatureBuilder<T>.feature(id: FeatureId<F>,
|
||||
feature(id.id, feature)
|
||||
|
||||
public class FeatureCollection<T : Any>(
|
||||
override val coordinateSpace: CoordinateSpace<T>,
|
||||
) : CoordinateSpace<T> by coordinateSpace, FeatureBuilder<T> {
|
||||
override val space: CoordinateSpace<T>,
|
||||
) : CoordinateSpace<T> by space, FeatureBuilder<T> {
|
||||
|
||||
@PublishedApi
|
||||
internal val featureMap: MutableMap<String, Feature<T>> = mutableStateMapOf()
|
||||
internal val featureMap: SnapshotStateMap<String, Feature<T>> = mutableStateMapOf()
|
||||
|
||||
public val features: Map<FeatureId<*>, Feature<T>>
|
||||
get() = featureMap.mapKeys { FeatureId<Feature<T>>(it.key) }
|
||||
@ -51,31 +54,47 @@ public class FeatureCollection<T : Any>(
|
||||
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 fun generateID(feature: Feature<T>): String = "@feature[${feature.hashCode().toUInt()}]"
|
||||
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 ?: generateID(feature)
|
||||
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]
|
||||
|
||||
override fun <F : Feature<T>, V> setAttribute(id: FeatureId<F>, key: Feature.Attribute<V>, value: V?) {
|
||||
get(id).attributes[key] = value
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun FeatureId<DraggableFeature<T>>.onDrag(
|
||||
listener: PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
with(get(this)) {
|
||||
attributes[DragListenerAttribute] =
|
||||
(attributes[DragListenerAttribute] ?: emptySet()) + DragListener { event, from, to ->
|
||||
event.listener(from as ViewPoint<T>, to as ViewPoint<T>)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -89,7 +108,7 @@ public class FeatureCollection<T : Any>(
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun FeatureId<DraggableFeature<T>>.draggable(
|
||||
constraint: ((T) -> T)? = null,
|
||||
listener: (PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit)? = null
|
||||
listener: (PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit)? = null,
|
||||
) {
|
||||
if (getAttribute(this, DraggableAttribute) == null) {
|
||||
val handle = DragHandle.withPrimaryButton<Any> { event, start, end ->
|
||||
@ -107,7 +126,7 @@ public class FeatureCollection<T : Any>(
|
||||
DragResult(end, true)
|
||||
}
|
||||
}
|
||||
setAttribute(this, DraggableAttribute, handle)
|
||||
this.withAttribute(DraggableAttribute, handle)
|
||||
}
|
||||
|
||||
//Apply callback
|
||||
@ -116,6 +135,42 @@ public class FeatureCollection<T : Any>(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@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.
|
||||
*/
|
||||
@ -128,36 +183,6 @@ public class FeatureCollection<T : Any>(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <F : ClickableFeature<T>> FeatureId<F>.onClick(
|
||||
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
with(get(this)) {
|
||||
attributes[ClickableListenerAttribute] =
|
||||
(attributes[ClickableListenerAttribute] ?: emptySet()) + ClickListener { event, point ->
|
||||
event.onClick(point as ViewPoint<T>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <A> getAttribute(id: FeatureId<Feature<T>>, key: Feature.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: Feature.Attribute<A>,
|
||||
block: (id: FeatureId<*>, attributeValue: A) -> Unit,
|
||||
) {
|
||||
featureMap.entries.sortedByDescending { it.value.z }.forEach { (id, feature) ->
|
||||
feature.attributes[key]?.let {
|
||||
block(FeatureId<Feature<T>>(id), it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
||||
@ -190,7 +215,7 @@ public fun <T : Any> FeatureBuilder<T>.circle(
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<CircleFeature<T>> = feature(
|
||||
id, CircleFeature(coordinateSpace, center, zoomRange, size, color)
|
||||
id, CircleFeature(space, center, zoomRange, size, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.rectangle(
|
||||
@ -200,7 +225,7 @@ public fun <T : Any> FeatureBuilder<T>.rectangle(
|
||||
color: Color = defaultColor,
|
||||
id: String? = null,
|
||||
): FeatureId<RectangleFeature<T>> = feature(
|
||||
id, RectangleFeature(coordinateSpace, centerCoordinates, zoomRange, size, color)
|
||||
id, RectangleFeature(space, centerCoordinates, zoomRange, size, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.draw(
|
||||
@ -210,7 +235,7 @@ public fun <T : Any> FeatureBuilder<T>.draw(
|
||||
draw: DrawScope.() -> Unit,
|
||||
): FeatureId<DrawFeature<T>> = feature(
|
||||
id,
|
||||
DrawFeature(coordinateSpace, position, zoomRange, drawFeature = draw)
|
||||
DrawFeature(space, position, zoomRange, drawFeature = draw)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.line(
|
||||
@ -221,7 +246,7 @@ public fun <T : Any> FeatureBuilder<T>.line(
|
||||
id: String? = null,
|
||||
): FeatureId<LineFeature<T>> = feature(
|
||||
id,
|
||||
LineFeature(coordinateSpace, aCoordinates, bCoordinates, zoomRange, color)
|
||||
LineFeature(space, aCoordinates, bCoordinates, zoomRange, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.arc(
|
||||
@ -233,7 +258,7 @@ public fun <T : Any> FeatureBuilder<T>.arc(
|
||||
id: String? = null,
|
||||
): FeatureId<ArcFeature<T>> = feature(
|
||||
id,
|
||||
ArcFeature(coordinateSpace, oval, startAngle, arcLength, zoomRange, color)
|
||||
ArcFeature(space, oval, startAngle, arcLength, zoomRange, color)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.points(
|
||||
@ -242,9 +267,22 @@ public fun <T : Any> FeatureBuilder<T>.points(
|
||||
stroke: Float = 2f,
|
||||
color: Color = defaultColor,
|
||||
pointMode: PointMode = PointMode.Points,
|
||||
attributes: Attributes = Attributes.EMPTY,
|
||||
id: String? = null,
|
||||
): FeatureId<PointsFeature<T>> =
|
||||
feature(id, PointsFeature(coordinateSpace, points, zoomRange, stroke, color, pointMode))
|
||||
): 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,
|
||||
@ -256,7 +294,7 @@ public fun <T : Any> FeatureBuilder<T>.image(
|
||||
feature(
|
||||
id,
|
||||
VectorImageFeature(
|
||||
coordinateSpace,
|
||||
space,
|
||||
position,
|
||||
size,
|
||||
image,
|
||||
@ -269,8 +307,8 @@ public fun <T : Any> FeatureBuilder<T>.group(
|
||||
id: String? = null,
|
||||
builder: FeatureCollection<T>.() -> Unit,
|
||||
): FeatureId<FeatureGroup<T>> {
|
||||
val map = FeatureCollection(coordinateSpace).apply(builder).features
|
||||
val feature = FeatureGroup(coordinateSpace, map, zoomRange)
|
||||
val map = FeatureCollection(space).apply(builder).features
|
||||
val feature = FeatureGroup(space, map, zoomRange)
|
||||
return feature(id, feature)
|
||||
}
|
||||
|
||||
@ -281,7 +319,7 @@ public fun <T : Any> FeatureBuilder<T>.scalableImage(
|
||||
painter: @Composable () -> Painter,
|
||||
): FeatureId<ScalableImageFeature<T>> = feature(
|
||||
id,
|
||||
ScalableImageFeature<T>(coordinateSpace, box, zoomRange, painter = painter)
|
||||
ScalableImageFeature<T>(space, box, zoomRange, painter = painter)
|
||||
)
|
||||
|
||||
public fun <T : Any> FeatureBuilder<T>.text(
|
||||
@ -293,5 +331,5 @@ public fun <T : Any> FeatureBuilder<T>.text(
|
||||
id: String? = null,
|
||||
): FeatureId<TextFeature<T>> = feature(
|
||||
id,
|
||||
TextFeature(coordinateSpace, position, text, zoomRange, color, fontConfig = font)
|
||||
TextFeature(space, position, text, zoomRange, color, fontConfig = font)
|
||||
)
|
||||
|
@ -0,0 +1,37 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
///**
|
||||
// * A group of other features
|
||||
// */
|
||||
//public data class FeatureGroup<T : Any>(
|
||||
// val parentBuilder: FeatureBuilder<T>,
|
||||
// private val groupId: String,
|
||||
// public override val zoomRange: FloatRange,
|
||||
// override val attributes: Attributes = Attributes.EMPTY,
|
||||
//) : FeatureBuilder<T>, Feature<T> {
|
||||
//
|
||||
// override val space: CoordinateSpace<T> get() = parentBuilder.space
|
||||
//
|
||||
// override fun generateUID(feature: Feature<T>?): String = parentBuilder.generateUID(feature)
|
||||
//
|
||||
// override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureId<F> =
|
||||
// parentBuilder.feature("${groupId}.${id ?: parentBuilder.generateUID(feature)}", feature)
|
||||
//
|
||||
// override fun <F : Feature<T>, V> FeatureId<F>.withAttribute(key: Attribute<V>, value: V?): FeatureId<F> =
|
||||
// with(parentBuilder) {
|
||||
// FeatureId<F>("${groupId}.${this@withAttribute.id}").withAttribute(key, value)
|
||||
// }
|
||||
//
|
||||
// override fun getBoundingBox(zoom: Float): Rectangle<T>? {
|
||||
// TODO("Not yet implemented")
|
||||
// }
|
||||
//
|
||||
// override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> =
|
||||
// copy(attributes = attributes.modify())
|
||||
//}
|
||||
//
|
||||
//public fun <T : Any> FeatureBuilder<T>.group(
|
||||
// zoomRange: FloatRange = defaultZoomRange,
|
||||
// id: String? = null,
|
||||
// builder: FeatureBuilder<T>.() -> Unit,
|
||||
//): FeatureId<FeatureGroup<T>> = feature(id, FeatureGroup(this, id ?: generateUID(null), zoomRange).apply(builder))
|
@ -4,13 +4,13 @@ import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
|
||||
public fun interface ClickListener<in T : Any> {
|
||||
public fun handle(event: PointerEvent, click: ViewPoint<T>): Unit
|
||||
public fun interface MouseListener<in T : Any> {
|
||||
public fun handle(event: PointerEvent, point: ViewPoint<T>): Unit
|
||||
|
||||
public companion object {
|
||||
public fun <T : Any> withPrimaryButton(
|
||||
block: (event: PointerEvent, click: ViewPoint<T>) -> Unit,
|
||||
): ClickListener<T> = ClickListener { event, click ->
|
||||
): MouseListener<T> = MouseListener { event, click ->
|
||||
if (event.buttons.isPrimaryPressed) {
|
||||
block(event, click)
|
||||
}
|
||||
@ -20,7 +20,7 @@ public fun interface ClickListener<in T : Any> {
|
||||
|
||||
public data class ViewConfig<T : Any>(
|
||||
val zoomSpeed: Float = 1f / 3f,
|
||||
val onClick: ClickListener<T>? = null,
|
||||
val onClick: MouseListener<T>? = null,
|
||||
val dragHandle: DragHandle<T>? = null,
|
||||
val onViewChange: ViewPoint<T>.() -> Unit = {},
|
||||
val onSelect: (Rectangle<T>) -> Unit = {},
|
||||
|
@ -24,27 +24,31 @@ public fun <T : Any> Modifier.mapControls(
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent()
|
||||
val coordinates = event.changes.first().position.toDpOffset().toCoordinates()
|
||||
val point = space.ViewPoint(coordinates, zoom)
|
||||
|
||||
val sortedFeatures =features.values.sortedByDescending { it.z }
|
||||
|
||||
if (event.type == PointerEventType.Move) {
|
||||
for (feature in sortedFeatures) {
|
||||
val listeners = (feature as? DomainFeature)?.attributes?.get(HoverListenerAttribute)
|
||||
if (listeners != null && point in feature) {
|
||||
listeners.forEach { it.handle(event, point) }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event.type == PointerEventType.Release) {
|
||||
val coordinates = event.changes.first().position.toDpOffset().toCoordinates()
|
||||
val viewPoint = space.ViewPoint(coordinates, zoom)
|
||||
config.onClick?.handle(
|
||||
event,
|
||||
viewPoint
|
||||
point
|
||||
)
|
||||
features.values.mapNotNull { feature ->
|
||||
val clickableFeature = feature as? ClickableFeature
|
||||
?: return@mapNotNull null
|
||||
val listeners = clickableFeature.attributes[ClickableListenerAttribute]
|
||||
?: return@mapNotNull null
|
||||
if (viewPoint in clickableFeature) {
|
||||
feature to listeners
|
||||
} else {
|
||||
null
|
||||
for (feature in sortedFeatures) {
|
||||
val listeners = (feature as? DomainFeature)?.attributes?.get(ClickListenerAttribute)
|
||||
if (listeners != null && point in feature) {
|
||||
listeners.forEach { it.handle(event, point) }
|
||||
break
|
||||
}
|
||||
}.maxByOrNull {
|
||||
it.first.z
|
||||
}?.second?.forEach {
|
||||
it.handle(event, viewPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package center.sciprog.maps.features
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||
@ -24,7 +25,9 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
state: CoordinateViewScope<T>,
|
||||
painterCache: Map<PainterFeature<T>, Painter>,
|
||||
feature: Feature<T>,
|
||||
|
||||
): Unit = with(state) {
|
||||
val alpha = feature.attributes[AlphaAttribute]?:1f
|
||||
fun T.toOffset(): Offset = toOffset(this@drawFeature)
|
||||
|
||||
when (feature) {
|
||||
@ -57,7 +60,8 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
useCenter = false,
|
||||
topLeft = dpRect.topLeft,
|
||||
size = size,
|
||||
style = Stroke()
|
||||
style = Stroke(),
|
||||
alpha = alpha
|
||||
)
|
||||
|
||||
}
|
||||
@ -93,6 +97,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
}
|
||||
|
||||
is FeatureGroup -> {
|
||||
//do nothing
|
||||
feature.children.values.forEach {
|
||||
drawFeature(state, painterCache, it)
|
||||
}
|
||||
@ -113,7 +118,23 @@ public fun <T : Any> DrawScope.drawFeature(
|
||||
points = points,
|
||||
color = feature.color,
|
||||
strokeWidth = feature.stroke,
|
||||
pointMode = feature.pointMode
|
||||
pointMode = feature.pointMode,
|
||||
alpha = alpha
|
||||
)
|
||||
}
|
||||
|
||||
is PolygonFeature -> {
|
||||
val points = feature.points.map { it.toOffset() }
|
||||
val last = points.last()
|
||||
val polygonPath = Path()
|
||||
polygonPath.moveTo(last.x, last.y)
|
||||
for ((x,y) in points){
|
||||
polygonPath.lineTo(x,y)
|
||||
}
|
||||
drawPath(
|
||||
path = polygonPath,
|
||||
color = feature.color,
|
||||
alpha = alpha
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -41,19 +41,17 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
||||
|
||||
is GeoJsonMultiPolygon -> group(id = id) {
|
||||
geometry.coordinates.forEach {
|
||||
points(
|
||||
polygon(
|
||||
it.first(),
|
||||
color = color,
|
||||
pointMode = PointMode.Polygon
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is GeoJsonPoint -> circle(geometry.coordinates, color = color, id = id)
|
||||
is GeoJsonPolygon -> points(
|
||||
is GeoJsonPolygon -> polygon(
|
||||
geometry.coordinates.first(),
|
||||
color = color,
|
||||
pointMode = PointMode.Polygon
|
||||
)
|
||||
|
||||
is GeoJsonGeometryCollection -> group(id = id) {
|
||||
@ -61,6 +59,8 @@ public fun FeatureBuilder<Gmc>.geoJsonGeometry(
|
||||
geoJsonGeometry(it)
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
withAttribute(AlphaAttribute, 0.5f)
|
||||
}
|
||||
|
||||
public fun FeatureBuilder<Gmc>.geoJsonFeature(
|
||||
|
@ -6,6 +6,7 @@ import androidx.compose.ui.unit.dp
|
||||
import center.sciprog.maps.features.CoordinateSpace
|
||||
import center.sciprog.maps.features.Rectangle
|
||||
import center.sciprog.maps.features.ViewPoint
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
object XYCoordinateSpace : CoordinateSpace<XY> {
|
||||
@ -70,4 +71,11 @@ object XYCoordinateSpace : CoordinateSpace<XY> {
|
||||
(b.x - x).dp * zoom,
|
||||
(b.y - y).dp * zoom
|
||||
)
|
||||
|
||||
override fun XY.isInsidePolygon(points: List<XY>): Boolean = points.zipWithNext().count { (left, right) ->
|
||||
val dist = right.y - left.y
|
||||
val intersection = left.y * abs((right.x - x) / dist) +
|
||||
right.y * abs((x - left.x) / dist)
|
||||
x in left.x..right.x && intersection >= y
|
||||
} % 2 == 0
|
||||
}
|
@ -25,9 +25,13 @@ fun FeatureBuilder<XY>.background(
|
||||
)
|
||||
return feature(
|
||||
id,
|
||||
ScalableImageFeature(coordinateSpace, box, zoomRange = defaultZoomRange, painter = painter).apply {
|
||||
z = -100f
|
||||
}
|
||||
ScalableImageFeature(
|
||||
space,
|
||||
box,
|
||||
zoomRange = defaultZoomRange,
|
||||
painter = painter,
|
||||
attributes = Attributes(ZAttribute, -100f)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -68,7 +72,8 @@ public fun FeatureBuilder<XY>.arc(
|
||||
startAngle = startAngle,
|
||||
arcLength = arcLength,
|
||||
zoomRange = zoomRange,
|
||||
color = color
|
||||
color = color,
|
||||
id = id
|
||||
)
|
||||
|
||||
fun FeatureBuilder<XY>.image(
|
||||
|
Loading…
Reference in New Issue
Block a user