Split points and multiline

This commit is contained in:
Alexander Nozik 2023-02-13 16:49:36 +03:00
parent 3219e13fa7
commit 90eb7b4575
12 changed files with 173 additions and 92 deletions

View File

@ -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()

View File

@ -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)
} }
} }
} }

View File

@ -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 ->

View File

@ -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
@ -32,7 +31,7 @@ public fun FeatureGroup<Gmc>.rectangle(
size: DpSize = DpSize(5.dp, 5.dp), size: DpSize = DpSize(5.dp, 5.dp),
id: String? = null, id: String? = null,
): FeatureRef<Gmc, RectangleFeature<Gmc>> = feature( ): FeatureRef<Gmc, RectangleFeature<Gmc>> = feature(
id, RectangleFeature(space, coordinatesOf(centerCoordinates), size) id, RectangleFeature(space, coordinatesOf(centerCoordinates), size)
) )
@ -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>,

View File

@ -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

View File

@ -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(

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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) {

View File

@ -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(

View File

@ -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()