Split points and multiline
This commit is contained in:
parent
3219e13fa7
commit
90eb7b4575
@ -10,7 +10,7 @@ val kmathVersion: String by extra("0.3.1-dev-10")
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "center.sciprog"
|
group = "center.sciprog"
|
||||||
version = "0.2.2-dev-4"
|
version = "0.2.2-dev-5"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.PointMode
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -91,7 +90,7 @@ fun App() {
|
|||||||
println("line 3 clicked")
|
println("line 3 clicked")
|
||||||
}
|
}
|
||||||
|
|
||||||
points(
|
multiLine(
|
||||||
points = listOf(
|
points = listOf(
|
||||||
55.742465 to 37.615812,
|
55.742465 to 37.615812,
|
||||||
55.742713 to 37.616370,
|
55.742713 to 37.616370,
|
||||||
@ -100,7 +99,6 @@ fun App() {
|
|||||||
55.742086 to 37.616566,
|
55.742086 to 37.616566,
|
||||||
55.741715 to 37.616716
|
55.741715 to 37.616716
|
||||||
),
|
),
|
||||||
pointMode = PointMode.Polygon
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//remember feature ID
|
//remember feature ID
|
||||||
@ -138,13 +136,11 @@ fun App() {
|
|||||||
println("Click on ${ref.id}")
|
println("Click on ${ref.id}")
|
||||||
//draw in top-level scope
|
//draw in top-level scope
|
||||||
with(this@MapView) {
|
with(this@MapView) {
|
||||||
points(
|
multiLine(
|
||||||
ref.resolve().points,
|
ref.resolve().points,
|
||||||
stroke = 4f,
|
|
||||||
pointMode = PointMode.Polygon,
|
|
||||||
attributes = Attributes(ZAttribute, 10f),
|
attributes = Attributes(ZAttribute, 10f),
|
||||||
id = "selected",
|
id = "selected",
|
||||||
).color(Color.Magenta)
|
).modifyAttribute(StrokeAttribute, 4f).color(Color.Magenta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import androidx.compose.material.MaterialTheme
|
|||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import androidx.compose.ui.graphics.PointMode
|
|
||||||
import androidx.compose.ui.input.pointer.isSecondaryPressed
|
import androidx.compose.ui.input.pointer.isSecondaryPressed
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.application
|
import androidx.compose.ui.window.application
|
||||||
@ -26,9 +25,8 @@ fun App() {
|
|||||||
val myPolygon: SnapshotStateList<XY> = remember { mutableStateListOf<XY>() }
|
val myPolygon: SnapshotStateList<XY> = remember { mutableStateListOf<XY>() }
|
||||||
|
|
||||||
val featureState: FeatureGroup<XY> = FeatureGroup.remember(XYCoordinateSpace) {
|
val featureState: FeatureGroup<XY> = FeatureGroup.remember(XYCoordinateSpace) {
|
||||||
points(
|
multiLine(
|
||||||
listOf(XY(0f, 0f), XY(0f, 1f), XY(1f, 1f), XY(1f, 0f), XY(0f, 0f)),
|
listOf(XY(0f, 0f), XY(0f, 1f), XY(1f, 1f), XY(1f, 0f), XY(0f, 0f)),
|
||||||
pointMode = PointMode.Polygon,
|
|
||||||
id = "frame"
|
id = "frame"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -36,9 +34,8 @@ fun App() {
|
|||||||
|
|
||||||
if(myPolygon.isNotEmpty()) {
|
if(myPolygon.isNotEmpty()) {
|
||||||
featureState.group("polygon") {
|
featureState.group("polygon") {
|
||||||
points(
|
multiLine(
|
||||||
myPolygon + myPolygon.first(),
|
myPolygon + myPolygon.first(),
|
||||||
pointMode = PointMode.Polygon,
|
|
||||||
)
|
)
|
||||||
myPolygon.forEachIndexed { index, xy ->
|
myPolygon.forEachIndexed { index, xy ->
|
||||||
circle(xy, id = "point[$index]").draggable { _, to ->
|
circle(xy, id = "point[$index]").draggable { _, to ->
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package center.sciprog.maps.compose
|
package center.sciprog.maps.compose
|
||||||
|
|
||||||
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
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
@ -83,11 +82,13 @@ public fun FeatureGroup<Gmc>.arc(
|
|||||||
|
|
||||||
public fun FeatureGroup<Gmc>.points(
|
public fun FeatureGroup<Gmc>.points(
|
||||||
points: List<Pair<Double, Double>>,
|
points: List<Pair<Double, Double>>,
|
||||||
stroke: Float = 2f,
|
|
||||||
pointMode: PointMode = PointMode.Points,
|
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<Gmc, PointsFeature<Gmc>> =
|
): FeatureRef<Gmc, PointsFeature<Gmc>> = feature(id, PointsFeature(space, points.map(::coordinatesOf)))
|
||||||
feature(id, PointsFeature(space, points.map(::coordinatesOf), stroke, pointMode))
|
|
||||||
|
public fun FeatureGroup<Gmc>.multiLine(
|
||||||
|
points: List<Pair<Double, Double>>,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureRef<Gmc, MultiLineFeature<Gmc>> = feature(id, MultiLineFeature(space, points.map(::coordinatesOf)))
|
||||||
|
|
||||||
public fun FeatureGroup<Gmc>.image(
|
public fun FeatureGroup<Gmc>.image(
|
||||||
position: Pair<Double, Double>,
|
position: Pair<Double, Double>,
|
||||||
|
@ -3,7 +3,10 @@ package center.sciprog.maps.features
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.Path
|
||||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
import androidx.compose.ui.graphics.drawscope.DrawStyle
|
import androidx.compose.ui.graphics.drawscope.DrawStyle
|
||||||
import androidx.compose.ui.graphics.drawscope.Fill
|
import androidx.compose.ui.graphics.drawscope.Fill
|
||||||
@ -112,8 +115,6 @@ 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>,
|
||||||
public val stroke: Float = 2f,
|
|
||||||
public val pointMode: PointMode = PointMode.Points,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : Feature<T> {
|
) : Feature<T> {
|
||||||
|
|
||||||
@ -125,6 +126,59 @@ public data class PointsFeature<T : Any>(
|
|||||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface LineSegmentFeature<T : Any> : Feature<T>
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
public data class LineFeature<T : Any>(
|
||||||
|
override val space: CoordinateSpace<T>,
|
||||||
|
public val a: T,
|
||||||
|
public val b: T,
|
||||||
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
|
) : DomainFeature<T>, LineSegmentFeature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||||
|
space.Rectangle(a, b)
|
||||||
|
|
||||||
|
override fun contains(viewPoint: ViewPoint<T>): Boolean = with(space) {
|
||||||
|
viewPoint.focus in getBoundingBox(viewPoint.zoom) && viewPoint.focus.distanceToLine(
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
viewPoint.zoom
|
||||||
|
).value < clickRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||||
|
}
|
||||||
|
|
||||||
|
public data class MultiLineFeature<T : Any>(
|
||||||
|
override val space: CoordinateSpace<T>,
|
||||||
|
public val points: List<T>,
|
||||||
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
|
) : DomainFeature<T>, LineSegmentFeature<T> {
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
override fun contains(viewPoint: ViewPoint<T>): Boolean = with(space) {
|
||||||
|
val boundingBox = getBoundingBox(viewPoint.zoom) ?: return@with false
|
||||||
|
viewPoint.focus in boundingBox && points.zipWithNext().minOf { (a, b) ->
|
||||||
|
viewPoint.focus.distanceToLine(
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
viewPoint.zoom
|
||||||
|
).value
|
||||||
|
} < clickRadius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val <T : Any, F : LineSegmentFeature<T>> F.clickRadius get() = attributes[ClickRadius] ?: 10f
|
||||||
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
public data class PolygonFeature<T : Any>(
|
public data class PolygonFeature<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
@ -176,29 +230,6 @@ public data class RectangleFeature<T : Any>(
|
|||||||
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
|
||||||
public data class LineFeature<T : Any>(
|
|
||||||
override val space: CoordinateSpace<T>,
|
|
||||||
public val a: T,
|
|
||||||
public val b: T,
|
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
|
||||||
) : DomainFeature<T> {
|
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
|
||||||
space.Rectangle(a, b)
|
|
||||||
|
|
||||||
private val clickRadius get() = attributes[ClickRadius] ?: 10f
|
|
||||||
|
|
||||||
override fun contains(viewPoint: ViewPoint<T>): Boolean = with(space) {
|
|
||||||
viewPoint.focus in getBoundingBox(viewPoint.zoom) && viewPoint.focus.distanceToLine(
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
viewPoint.zoom
|
|
||||||
).value < clickRadius
|
|
||||||
}
|
|
||||||
|
|
||||||
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 startAngle the angle from 3 o'clock downwards for the start of the arc in radians
|
||||||
* @param arcLength arc length in radians
|
* @param arcLength arc length in radians
|
||||||
|
@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.mutableStateMapOf
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
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.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@ -156,56 +155,68 @@ public inline fun <T : Any, reified F : Feature<T>> FeatureGroup<T>.forEachWithT
|
|||||||
public fun <T : Any> FeatureGroup<T>.circle(
|
public fun <T : Any> FeatureGroup<T>.circle(
|
||||||
center: T,
|
center: T,
|
||||||
size: Dp = 5.dp,
|
size: Dp = 5.dp,
|
||||||
|
attributes: Attributes = Attributes.EMPTY,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<T, CircleFeature<T>> = feature(
|
): FeatureRef<T, CircleFeature<T>> = feature(
|
||||||
id, CircleFeature(space, center, size)
|
id, CircleFeature(space, center, size, attributes)
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <T : Any> FeatureGroup<T>.rectangle(
|
public fun <T : Any> FeatureGroup<T>.rectangle(
|
||||||
centerCoordinates: T,
|
centerCoordinates: T,
|
||||||
size: DpSize = DpSize(5.dp, 5.dp),
|
size: DpSize = DpSize(5.dp, 5.dp),
|
||||||
|
attributes: Attributes = Attributes.EMPTY,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<T, RectangleFeature<T>> = feature(
|
): FeatureRef<T, RectangleFeature<T>> = feature(
|
||||||
id, RectangleFeature(space, centerCoordinates, size)
|
id, RectangleFeature(space, centerCoordinates, size, attributes)
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <T : Any> FeatureGroup<T>.draw(
|
public fun <T : Any> FeatureGroup<T>.draw(
|
||||||
position: T,
|
position: T,
|
||||||
|
attributes: Attributes = Attributes.EMPTY,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
draw: DrawScope.() -> Unit,
|
draw: DrawScope.() -> Unit,
|
||||||
): FeatureRef<T, DrawFeature<T>> = feature(
|
): FeatureRef<T, DrawFeature<T>> = feature(
|
||||||
id,
|
id,
|
||||||
DrawFeature(space, position, drawFeature = draw)
|
DrawFeature(space, position, drawFeature = draw, attributes = attributes)
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <T : Any> FeatureGroup<T>.line(
|
public fun <T : Any> FeatureGroup<T>.line(
|
||||||
aCoordinates: T,
|
aCoordinates: T,
|
||||||
bCoordinates: T,
|
bCoordinates: T,
|
||||||
|
attributes: Attributes = Attributes.EMPTY,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<T, LineFeature<T>> = feature(
|
): FeatureRef<T, LineFeature<T>> = feature(
|
||||||
id,
|
id,
|
||||||
LineFeature(space, aCoordinates, bCoordinates)
|
LineFeature(space, aCoordinates, bCoordinates, attributes)
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <T : Any> FeatureGroup<T>.arc(
|
public fun <T : Any> FeatureGroup<T>.arc(
|
||||||
oval: Rectangle<T>,
|
oval: Rectangle<T>,
|
||||||
startAngle: Angle,
|
startAngle: Angle,
|
||||||
arcLength: Angle,
|
arcLength: Angle,
|
||||||
|
attributes: Attributes = Attributes.EMPTY,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<T, ArcFeature<T>> = feature(
|
): FeatureRef<T, ArcFeature<T>> = feature(
|
||||||
id,
|
id,
|
||||||
ArcFeature(space, oval, startAngle, arcLength)
|
ArcFeature(space, oval, startAngle, arcLength, attributes)
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <T : Any> FeatureGroup<T>.points(
|
public fun <T : Any> FeatureGroup<T>.points(
|
||||||
points: List<T>,
|
points: List<T>,
|
||||||
stroke: Float = 2f,
|
|
||||||
pointMode: PointMode = PointMode.Points,
|
|
||||||
attributes: Attributes = Attributes.EMPTY,
|
attributes: Attributes = Attributes.EMPTY,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<T, PointsFeature<T>> = feature(
|
): FeatureRef<T, PointsFeature<T>> = feature(
|
||||||
id,
|
id,
|
||||||
PointsFeature(space, points, stroke, pointMode, attributes)
|
PointsFeature(space, points, attributes)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun <T : Any> FeatureGroup<T>.multiLine(
|
||||||
|
points: List<T>,
|
||||||
|
attributes: Attributes = Attributes.EMPTY,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureRef<T, MultiLineFeature<T>> = feature(
|
||||||
|
id,
|
||||||
|
MultiLineFeature(space, points, attributes)
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <T : Any> FeatureGroup<T>.polygon(
|
public fun <T : Any> FeatureGroup<T>.polygon(
|
||||||
|
@ -32,8 +32,18 @@ public object VisibleAttribute : Attribute<Boolean>
|
|||||||
|
|
||||||
public object ColorAttribute : Attribute<Color>
|
public object ColorAttribute : Attribute<Color>
|
||||||
|
|
||||||
|
|
||||||
|
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.color(color: Color): FeatureRef<T, F> =
|
||||||
|
modifyAttribute(ColorAttribute, color)
|
||||||
|
|
||||||
|
|
||||||
public object ZoomRangeAttribute : Attribute<FloatRange>
|
public object ZoomRangeAttribute : Attribute<FloatRange>
|
||||||
|
|
||||||
|
|
||||||
|
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.zoomRange(range: FloatRange): FeatureRef<T, F> =
|
||||||
|
modifyAttribute(ZoomRangeAttribute, range)
|
||||||
|
|
||||||
|
|
||||||
public object AlphaAttribute : Attribute<Float>
|
public object AlphaAttribute : Attribute<Float>
|
||||||
|
|
||||||
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.modifyAttributes(modify: AttributesBuilder.() -> Unit): FeatureRef<T, F> {
|
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.modifyAttributes(modify: AttributesBuilder.() -> Unit): FeatureRef<T, F> {
|
||||||
@ -47,7 +57,10 @@ public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.modifyAttributes(modify: A
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any, F : Feature<T>, V> FeatureRef<T, F>.modifyAttribute(key: Attribute<V>, value: V?): FeatureRef<T, F>{
|
public fun <T : Any, F : Feature<T>, V> FeatureRef<T, F>.modifyAttribute(
|
||||||
|
key: Attribute<V>,
|
||||||
|
value: V?,
|
||||||
|
): FeatureRef<T, F> {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
parent.feature(id, resolve().withAttributes { withAttribute(key, value) } as F)
|
parent.feature(id, resolve().withAttributes { withAttribute(key, value) } as F)
|
||||||
return this
|
return this
|
||||||
@ -59,10 +72,10 @@ public fun <T : Any, F : Feature<T>, V> FeatureRef<T, F>.modifyAttribute(key: A
|
|||||||
* @param constraint optional drag constraint
|
* @param constraint optional drag constraint
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public fun <T: Any, F : DraggableFeature<T>> FeatureRef<T, F>.draggable(
|
public fun <T : Any, F : DraggableFeature<T>> FeatureRef<T, F>.draggable(
|
||||||
constraint: ((T) -> T)? = null,
|
constraint: ((T) -> T)? = null,
|
||||||
listener: (PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit)? = null,
|
listener: (PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit)? = null,
|
||||||
): FeatureRef<T, F> = with(parent){
|
): FeatureRef<T, F> = with(parent) {
|
||||||
if (attributes[DraggableAttribute] == null) {
|
if (attributes[DraggableAttribute] == null) {
|
||||||
val handle = DragHandle.withPrimaryButton<Any> { event, start, end ->
|
val handle = DragHandle.withPrimaryButton<Any> { event, start, end ->
|
||||||
val feature = featureMap[id] as? DraggableFeature<T> ?: return@withPrimaryButton DragResult(end)
|
val feature = featureMap[id] as? DraggableFeature<T> ?: return@withPrimaryButton DragResult(end)
|
||||||
@ -112,7 +125,7 @@ public fun <T : Any, F : DomainFeature<T>> FeatureRef<T, F>.onClick(
|
|||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public fun <T: Any, F : DomainFeature<T>> FeatureRef<T, F>.onClick(
|
public fun <T : Any, F : DomainFeature<T>> FeatureRef<T, F>.onClick(
|
||||||
pointerMatcher: PointerMatcher,
|
pointerMatcher: PointerMatcher,
|
||||||
keyboardModifiers: PointerKeyboardModifiers.() -> Boolean = { true },
|
keyboardModifiers: PointerKeyboardModifiers.() -> Boolean = { true },
|
||||||
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
|
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
|
||||||
@ -127,7 +140,7 @@ public fun <T: Any, F : DomainFeature<T>> FeatureRef<T, F>.onClick(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public fun <T: Any, F : DomainFeature<T>> FeatureRef<T, F>.onHover(
|
public fun <T : Any, F : DomainFeature<T>> FeatureRef<T, F>.onHover(
|
||||||
onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
|
onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
|
||||||
): FeatureRef<T, F> = modifyAttributes {
|
): FeatureRef<T, F> = modifyAttributes {
|
||||||
HoverListenerAttribute.add(
|
HoverListenerAttribute.add(
|
||||||
@ -147,15 +160,13 @@ public fun <T: Any, F : DomainFeature<T>> FeatureRef<T, F>.onHover(
|
|||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
|
|
||||||
public fun <T: Any, F : Feature<T>> FeatureRef<T, F>.color(color: Color): FeatureRef<T, F> =
|
|
||||||
modifyAttribute(ColorAttribute, color)
|
|
||||||
|
|
||||||
public fun <T: Any, F : Feature<T>> FeatureRef<T, F>.zoomRange(range: FloatRange): FeatureRef<T, F> =
|
public object PathEffectAttribute : Attribute<PathEffect>
|
||||||
modifyAttribute(ZoomRangeAttribute, range)
|
|
||||||
|
|
||||||
|
public fun <T : Any> FeatureRef<T, LineSegmentFeature<T>>.pathEffect(effect: PathEffect): FeatureRef<T, LineSegmentFeature<T>> =
|
||||||
|
|
||||||
public object PathEffectAttribute: Attribute<PathEffect>
|
|
||||||
|
|
||||||
public fun <T: Any> FeatureRef<T, PointsFeature<T>>.pathEffect(effect: PathEffect): FeatureRef<T, PointsFeature<T>> =
|
|
||||||
modifyAttribute(PathEffectAttribute, effect)
|
modifyAttribute(PathEffectAttribute, effect)
|
||||||
|
|
||||||
|
public object StrokeAttribute : Attribute<Float>
|
||||||
|
|
||||||
|
public fun <T : Any, F : LineSegmentFeature<T>> FeatureRef<T, F>.stroke(width: Float): FeatureRef<T, F> =
|
||||||
|
modifyAttribute(StrokeAttribute, width)
|
@ -94,13 +94,13 @@ public suspend fun PointerInputScope.detectClicks(
|
|||||||
if (upOrCancel != null) {
|
if (upOrCancel != null) {
|
||||||
// tap was successful.
|
// tap was successful.
|
||||||
if (onDoubleClick == null) {
|
if (onDoubleClick == null) {
|
||||||
onClick?.invoke(this, upOrCancel) // no need to check for double-tap.
|
onClick?.invoke(this, down) // no need to check for double-tap.
|
||||||
} else {
|
} else {
|
||||||
// check for second tap
|
// check for second tap
|
||||||
val secondDown = awaitSecondDown(upOrCancel.firstChange)
|
val secondDown = awaitSecondDown(upOrCancel.firstChange)
|
||||||
|
|
||||||
if (secondDown == null) {
|
if (secondDown == null) {
|
||||||
onClick?.invoke(this, upOrCancel) // no valid second tap started
|
onClick?.invoke(this, down) // no valid second tap started
|
||||||
} else {
|
} else {
|
||||||
// Second tap down detected
|
// Second tap down detected
|
||||||
pressScope.reset()
|
pressScope.reset()
|
||||||
@ -115,16 +115,16 @@ public suspend fun PointerInputScope.detectClicks(
|
|||||||
if (secondUp != null) {
|
if (secondUp != null) {
|
||||||
secondUp.consume()
|
secondUp.consume()
|
||||||
pressScope.release()
|
pressScope.release()
|
||||||
onDoubleClick(down)
|
onDoubleClick(secondDown)
|
||||||
} else {
|
} else {
|
||||||
pressScope.cancel()
|
pressScope.cancel()
|
||||||
onClick?.invoke(this, upOrCancel)
|
onClick?.invoke(this, down)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: PointerEventTimeoutCancellationException) {
|
} catch (e: PointerEventTimeoutCancellationException) {
|
||||||
// The first tap was valid, but the second tap is a long press.
|
// The first tap was valid, but the second tap is a long press.
|
||||||
// notify for the first tap
|
// notify for the first tap
|
||||||
onClick?.invoke(this, upOrCancel)
|
onClick?.invoke(this, down)
|
||||||
|
|
||||||
// notify for the long press
|
// notify for the long press
|
||||||
onLongClick?.invoke(this, secondDown)
|
onLongClick?.invoke(this, secondDown)
|
||||||
@ -209,7 +209,6 @@ private suspend fun AwaitPointerEventScope.awaitSecondDown(
|
|||||||
/**
|
/**
|
||||||
* Reads events until the first down is received. If [requireUnconsumed] is `true` and the first
|
* Reads events until the first down is received. If [requireUnconsumed] is `true` and the first
|
||||||
* down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored.
|
* down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored.
|
||||||
* If it was down caused by [PointerType.Mouse], this function reacts only on primary button.
|
|
||||||
*/
|
*/
|
||||||
internal suspend fun AwaitPointerEventScope.awaitFirstDownEvent(
|
internal suspend fun AwaitPointerEventScope.awaitFirstDownEvent(
|
||||||
requireUnconsumed: Boolean = true,
|
requireUnconsumed: Boolean = true,
|
||||||
|
@ -2,15 +2,12 @@ package center.sciprog.maps.features
|
|||||||
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.graphics.Path
|
|
||||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||||
import androidx.compose.ui.graphics.drawscope.translate
|
import androidx.compose.ui.graphics.drawscope.translate
|
||||||
import androidx.compose.ui.graphics.nativeCanvas
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import center.sciprog.attributes.plus
|
import center.sciprog.attributes.plus
|
||||||
import org.jetbrains.skia.Font
|
import org.jetbrains.skia.Font
|
||||||
import org.jetbrains.skia.Paint
|
import org.jetbrains.skia.Paint
|
||||||
@ -28,7 +25,7 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
feature: Feature<T>,
|
feature: Feature<T>,
|
||||||
): Unit = with(state) {
|
): Unit = with(state) {
|
||||||
val color = feature.color ?: Color.Red
|
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) {
|
||||||
@ -48,7 +45,14 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
size = feature.size.toSize()
|
size = feature.size.toSize()
|
||||||
)
|
)
|
||||||
|
|
||||||
is LineFeature -> drawLine(color, feature.a.toOffset(), feature.b.toOffset())
|
is LineFeature -> drawLine(
|
||||||
|
color,
|
||||||
|
feature.a.toOffset(),
|
||||||
|
feature.b.toOffset(),
|
||||||
|
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
||||||
|
pathEffect = feature.attributes[PathEffectAttribute]
|
||||||
|
)
|
||||||
|
|
||||||
is ArcFeature -> {
|
is ArcFeature -> {
|
||||||
val dpRect = feature.oval.toDpRect().toRect()
|
val dpRect = feature.oval.toDpRect().toRect()
|
||||||
|
|
||||||
@ -119,8 +123,20 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
drawPoints(
|
drawPoints(
|
||||||
points = points,
|
points = points,
|
||||||
color = color,
|
color = color,
|
||||||
strokeWidth = feature.stroke,
|
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
||||||
pointMode = feature.pointMode,
|
pointMode = PointMode.Points,
|
||||||
|
pathEffect = feature.attributes[PathEffectAttribute],
|
||||||
|
alpha = alpha
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is MultiLineFeature -> {
|
||||||
|
val points = feature.points.map { it.toOffset() }
|
||||||
|
drawPoints(
|
||||||
|
points = points,
|
||||||
|
color = color,
|
||||||
|
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
||||||
|
pointMode = PointMode.Polygon,
|
||||||
pathEffect = feature.attributes[PathEffectAttribute],
|
pathEffect = feature.attributes[PathEffectAttribute],
|
||||||
alpha = alpha
|
alpha = alpha
|
||||||
)
|
)
|
||||||
@ -131,8 +147,8 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
val last = points.last()
|
val last = points.last()
|
||||||
val polygonPath = Path()
|
val polygonPath = Path()
|
||||||
polygonPath.moveTo(last.x, last.y)
|
polygonPath.moveTo(last.x, last.y)
|
||||||
for ((x,y) in points){
|
for ((x, y) in points) {
|
||||||
polygonPath.lineTo(x,y)
|
polygonPath.lineTo(x, y)
|
||||||
}
|
}
|
||||||
drawPath(
|
drawPath(
|
||||||
path = polygonPath,
|
path = polygonPath,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package center.sciprog.maps.geojson
|
package center.sciprog.maps.geojson
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.PointMode
|
|
||||||
import center.sciprog.attributes.NameAttribute
|
import center.sciprog.attributes.NameAttribute
|
||||||
import center.sciprog.maps.coordinates.Gmc
|
import center.sciprog.maps.coordinates.Gmc
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
@ -17,23 +16,18 @@ public fun FeatureGroup<Gmc>.geoJsonGeometry(
|
|||||||
geometry: GeoJsonGeometry,
|
geometry: GeoJsonGeometry,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<Gmc, Feature<Gmc>> = when (geometry) {
|
): FeatureRef<Gmc, Feature<Gmc>> = when (geometry) {
|
||||||
is GeoJsonLineString -> points(
|
is GeoJsonLineString -> multiLine(
|
||||||
geometry.coordinates,
|
geometry.coordinates,
|
||||||
pointMode = PointMode.Lines
|
|
||||||
)
|
)
|
||||||
|
|
||||||
is GeoJsonMultiLineString -> group(id = id) {
|
is GeoJsonMultiLineString -> group(id = id) {
|
||||||
geometry.coordinates.forEach {
|
geometry.coordinates.forEach {
|
||||||
points(
|
multiLine(it)
|
||||||
it,
|
|
||||||
pointMode = PointMode.Lines
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is GeoJsonMultiPoint -> points(
|
is GeoJsonMultiPoint -> points(
|
||||||
geometry.coordinates,
|
geometry.coordinates,
|
||||||
pointMode = PointMode.Points
|
|
||||||
)
|
)
|
||||||
|
|
||||||
is GeoJsonMultiPolygon -> group(id = id) {
|
is GeoJsonMultiPolygon -> group(id = id) {
|
||||||
|
@ -51,7 +51,7 @@ fun FeatureGroup<XY>.line(
|
|||||||
aCoordinates: Pair<Number, Number>,
|
aCoordinates: Pair<Number, Number>,
|
||||||
bCoordinates: Pair<Number, Number>,
|
bCoordinates: Pair<Number, Number>,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<XY, LineFeature<XY>> = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), id)
|
): FeatureRef<XY, LineFeature<XY>> = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), id = id)
|
||||||
|
|
||||||
|
|
||||||
public fun FeatureGroup<XY>.arc(
|
public fun FeatureGroup<XY>.arc(
|
||||||
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PointMode
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||||
import androidx.compose.ui.graphics.drawscope.translate
|
import androidx.compose.ui.graphics.drawscope.translate
|
||||||
@ -77,6 +78,30 @@ fun FeatureStateSnapshot<XY>.generateSvg(
|
|||||||
alpha = alpha
|
alpha = alpha
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is PointsFeature -> {
|
||||||
|
val points = feature.points.map { it.toOffset() }
|
||||||
|
drawPoints(
|
||||||
|
points = points,
|
||||||
|
color = color,
|
||||||
|
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
||||||
|
pointMode = PointMode.Points,
|
||||||
|
pathEffect = feature.attributes[PathEffectAttribute],
|
||||||
|
alpha = alpha
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is MultiLineFeature -> {
|
||||||
|
val points = feature.points.map { it.toOffset() }
|
||||||
|
drawPoints(
|
||||||
|
points = points,
|
||||||
|
color = color,
|
||||||
|
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
||||||
|
pointMode = PointMode.Polygon,
|
||||||
|
pathEffect = feature.attributes[PathEffectAttribute],
|
||||||
|
alpha = alpha
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is ArcFeature -> {
|
is ArcFeature -> {
|
||||||
val topLeft = feature.oval.leftTop.toOffset()
|
val topLeft = feature.oval.leftTop.toOffset()
|
||||||
val bottomRight = feature.oval.rightBottom.toOffset()
|
val bottomRight = feature.oval.rightBottom.toOffset()
|
||||||
|
Loading…
Reference in New Issue
Block a user