Generalize feature draw logic
This commit is contained in:
parent
fb13fa1431
commit
5b95adc649
@ -2,10 +2,7 @@ package center.sciprog.maps.compose
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.*
|
||||||
import androidx.compose.ui.unit.DpOffset
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import center.sciprog.maps.coordinates.*
|
import center.sciprog.maps.coordinates.*
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
@ -18,9 +15,6 @@ internal class MapState internal constructor(
|
|||||||
) : CoordinateViewState<Gmc>(config, canvasSize, viewPoint) {
|
) : CoordinateViewState<Gmc>(config, canvasSize, viewPoint) {
|
||||||
override val space: CoordinateSpace<Gmc> get() = GmcCoordinateSpace
|
override val space: CoordinateSpace<Gmc> get() = GmcCoordinateSpace
|
||||||
|
|
||||||
public val zoom: Int
|
|
||||||
get() = floor(viewPoint.zoom).toInt()
|
|
||||||
|
|
||||||
public val scaleFactor: Double
|
public val scaleFactor: Double
|
||||||
get() = WebMercatorProjection.scaleFactor(viewPoint.zoom)
|
get() = WebMercatorProjection.scaleFactor(viewPoint.zoom)
|
||||||
|
|
||||||
@ -31,7 +25,7 @@ internal class MapState internal constructor(
|
|||||||
get() = 2.0.pow(viewPoint.zoom - zoom)
|
get() = 2.0.pow(viewPoint.zoom - zoom)
|
||||||
|
|
||||||
private fun DpOffset.toMercator(): WebMercatorCoordinates = WebMercatorCoordinates(
|
private fun DpOffset.toMercator(): WebMercatorCoordinates = WebMercatorCoordinates(
|
||||||
zoom,
|
floor(zoom).toInt(),
|
||||||
(x - canvasSize.width / 2).value / tileScale + centerCoordinates.x,
|
(x - canvasSize.width / 2).value / tileScale + centerCoordinates.x,
|
||||||
(y - canvasSize.height / 2).value / tileScale + centerCoordinates.y,
|
(y - canvasSize.height / 2).value / tileScale + centerCoordinates.y,
|
||||||
)
|
)
|
||||||
@ -50,6 +44,12 @@ internal class MapState internal constructor(
|
|||||||
override fun Gmc.toDpOffset(): DpOffset =
|
override fun Gmc.toDpOffset(): DpOffset =
|
||||||
WebMercatorProjection.toMercator(this, zoom).toOffset()
|
WebMercatorProjection.toMercator(this, zoom).toOffset()
|
||||||
|
|
||||||
|
override fun Rectangle<Gmc>.toDpRect(): DpRect {
|
||||||
|
val topLeft = topLeft.toDpOffset()
|
||||||
|
val bottomRight = bottomRight.toDpOffset()
|
||||||
|
return DpRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)
|
||||||
|
}
|
||||||
|
|
||||||
override fun viewPointFor(rectangle: Rectangle<Gmc>): ViewPoint<Gmc> {
|
override fun viewPointFor(rectangle: Rectangle<Gmc>): ViewPoint<Gmc> {
|
||||||
val zoom = log2(
|
val zoom = log2(
|
||||||
min(
|
min(
|
||||||
|
@ -4,22 +4,21 @@ import androidx.compose.foundation.Canvas
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.graphics.drawscope.*
|
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.vector.VectorPainter
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
|
||||||
import center.sciprog.maps.coordinates.Gmc
|
import center.sciprog.maps.coordinates.Gmc
|
||||||
import center.sciprog.maps.coordinates.radians
|
|
||||||
import center.sciprog.maps.coordinates.toFloat
|
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlinx.coroutines.supervisorScope
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.jetbrains.skia.Font
|
|
||||||
import org.jetbrains.skia.Paint
|
import org.jetbrains.skia.Paint
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
@ -51,8 +50,6 @@ public actual fun MapView(
|
|||||||
initialViewPoint,
|
initialViewPoint,
|
||||||
mapTileProvider.tileSize
|
mapTileProvider.tileSize
|
||||||
)
|
)
|
||||||
val canvasModifier = modifier.mapControls(state).fillMaxSize()
|
|
||||||
|
|
||||||
with(state) {
|
with(state) {
|
||||||
|
|
||||||
val mapTiles = remember(mapTileProvider) { mutableStateListOf<MapTile>() }
|
val mapTiles = remember(mapTileProvider) { mutableStateListOf<MapTile>() }
|
||||||
@ -74,7 +71,7 @@ public actual fun MapView(
|
|||||||
|
|
||||||
for (j in verticalIndices) {
|
for (j in verticalIndices) {
|
||||||
for (i in horizontalIndices) {
|
for (i in horizontalIndices) {
|
||||||
val id = TileId(zoom, i, j)
|
val id = TileId(floor(zoom).toInt(), i, j)
|
||||||
//ensure that failed tiles do not fail the application
|
//ensure that failed tiles do not fail the application
|
||||||
supervisorScope {
|
supervisorScope {
|
||||||
//start all
|
//start all
|
||||||
@ -96,110 +93,11 @@ public actual fun MapView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val painterCache = key(featuresState) {
|
val painterCache: Map<VectorImageFeature<Gmc>, VectorPainter> = key(featuresState) {
|
||||||
featuresState.features.values.filterIsInstance<VectorImageFeature<Gmc>>().associateWith { it.painter() }
|
featuresState.features.values.filterIsInstance<VectorImageFeature<Gmc>>().associateWith { it.painter() }
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas(canvasModifier) {
|
Canvas(modifier = modifier.mapControls(state).fillMaxSize()) {
|
||||||
fun GeodeticMapCoordinates.toOffset(): Offset = toOffset(this@Canvas)
|
|
||||||
|
|
||||||
fun DrawScope.drawFeature(zoom: Int, feature: MapFeature) {
|
|
||||||
when (feature) {
|
|
||||||
is FeatureSelector -> drawFeature(zoom, feature.selector(zoom))
|
|
||||||
is CircleFeature -> drawCircle(
|
|
||||||
feature.color,
|
|
||||||
feature.size.toPx(),
|
|
||||||
center = feature.center.toOffset()
|
|
||||||
)
|
|
||||||
|
|
||||||
is RectangleFeature -> drawRect(
|
|
||||||
feature.color,
|
|
||||||
topLeft = feature.center.toOffset() - Offset(
|
|
||||||
feature.size.width.toPx() / 2,
|
|
||||||
feature.size.height.toPx() / 2
|
|
||||||
),
|
|
||||||
size = feature.size.toSize()
|
|
||||||
)
|
|
||||||
|
|
||||||
is LineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
|
|
||||||
is ArcFeature -> {
|
|
||||||
val topLeft = feature.oval.topLeft.toOffset()
|
|
||||||
val bottomRight = feature.oval.bottomRight.toOffset()
|
|
||||||
|
|
||||||
val size = Size(abs(topLeft.x - bottomRight.x), abs(topLeft.y - bottomRight.y))
|
|
||||||
|
|
||||||
drawArc(
|
|
||||||
color = feature.color,
|
|
||||||
startAngle = feature.startAngle.radians.degrees.toFloat(),
|
|
||||||
sweepAngle = feature.arcLength.radians.degrees.toFloat(),
|
|
||||||
useCenter = false,
|
|
||||||
topLeft = topLeft,
|
|
||||||
size = size,
|
|
||||||
style = Stroke()
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
is BitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
|
|
||||||
|
|
||||||
is VectorImageFeature -> {
|
|
||||||
val offset = feature.position.toOffset()
|
|
||||||
val size = feature.size.toSize()
|
|
||||||
translate(offset.x - size.width / 2, offset.y - size.height / 2) {
|
|
||||||
with(painterCache[feature]!!) {
|
|
||||||
draw(size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is TextFeature -> drawIntoCanvas { canvas ->
|
|
||||||
val offset = feature.position.toOffset()
|
|
||||||
canvas.nativeCanvas.drawString(
|
|
||||||
feature.text,
|
|
||||||
offset.x + 5,
|
|
||||||
offset.y - 5,
|
|
||||||
Font().apply(feature.fontConfig),
|
|
||||||
feature.color.toPaint()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is DrawFeature -> {
|
|
||||||
val offset = feature.position.toOffset()
|
|
||||||
translate(offset.x, offset.y) {
|
|
||||||
feature.drawFeature(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is FeatureGroup -> {
|
|
||||||
feature.children.values.forEach {
|
|
||||||
drawFeature(zoom, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is PathFeature -> {
|
|
||||||
TODO("MapPathFeature not implemented")
|
|
||||||
// val offset = feature.rectangle.center.toOffset() - feature.targetRect.center
|
|
||||||
// translate(offset.x, offset.y) {
|
|
||||||
// sca
|
|
||||||
// drawPath(feature.path, brush = feature.brush, style = feature.style)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
is PointsFeature -> {
|
|
||||||
val points = feature.points.map { it.toOffset() }
|
|
||||||
drawPoints(
|
|
||||||
points = points,
|
|
||||||
color = feature.color,
|
|
||||||
strokeWidth = feature.stroke,
|
|
||||||
pointMode = feature.pointMode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// else -> {
|
|
||||||
// logger.error { "Unrecognized feature type: ${feature::class}" }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canvasSize != size.toDpSize()) {
|
if (canvasSize != size.toDpSize()) {
|
||||||
logger.debug { "Recalculate canvas. Size: $size" }
|
logger.debug { "Recalculate canvas. Size: $size" }
|
||||||
@ -226,7 +124,7 @@ public actual fun MapView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
featuresState.features.values.filter { viewPoint.zoom in it.zoomRange }.forEach { feature ->
|
featuresState.features.values.filter { viewPoint.zoom in it.zoomRange }.forEach { feature ->
|
||||||
drawFeature(zoom, feature)
|
drawFeature(state, painterCache, feature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,12 +26,12 @@ public object WebMercatorProjection {
|
|||||||
/**
|
/**
|
||||||
* https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas
|
* https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas
|
||||||
*/
|
*/
|
||||||
public fun toMercator(gmc: GeodeticMapCoordinates, zoom: Int): WebMercatorCoordinates {
|
public fun toMercator(gmc: GeodeticMapCoordinates, zoom: Double): WebMercatorCoordinates {
|
||||||
require(abs(gmc.latitude) <= MercatorProjection.MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
|
require(abs(gmc.latitude) <= MercatorProjection.MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
|
||||||
|
|
||||||
val scaleFactor = scaleFactor(zoom.toDouble())
|
val scaleFactor = scaleFactor(zoom)
|
||||||
return WebMercatorCoordinates(
|
return WebMercatorCoordinates(
|
||||||
zoom = zoom,
|
zoom = floor(zoom).toInt(),
|
||||||
x = scaleFactor * (gmc.longitude.radians.value + PI),
|
x = scaleFactor * (gmc.longitude.radians.value + PI),
|
||||||
y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.radians.value / 2)))
|
y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.radians.value / 2)))
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.unit.*
|
||||||
|
|
||||||
|
public abstract class CoordinateViewState<T : Any>(
|
||||||
|
public val config: ViewConfig<T>,
|
||||||
|
canvasSize: DpSize,
|
||||||
|
viewPoint: ViewPoint<T>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
public abstract val space: CoordinateSpace<T>
|
||||||
|
|
||||||
|
public var canvasSize: DpSize by mutableStateOf(canvasSize)
|
||||||
|
protected var viewPointState: MutableState<ViewPoint<T>> = mutableStateOf(viewPoint)
|
||||||
|
|
||||||
|
public var viewPoint: ViewPoint<T>
|
||||||
|
get() = viewPointState.value
|
||||||
|
set(value) {
|
||||||
|
config.onViewChange(value)
|
||||||
|
viewPointState.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public val zoom: Double get() = viewPoint.zoom
|
||||||
|
|
||||||
|
public abstract fun DpOffset.toCoordinates(): T
|
||||||
|
|
||||||
|
public abstract fun T.toDpOffset(): DpOffset
|
||||||
|
|
||||||
|
public fun T.toOffset(density: Density): Offset = with(density){
|
||||||
|
val dpOffset = this@toOffset.toDpOffset()
|
||||||
|
Offset(dpOffset.x.toPx(), dpOffset.y.toPx())
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract fun Rectangle<T>.toDpRect(): DpRect
|
||||||
|
|
||||||
|
public abstract fun ViewPoint<T>.moveBy(x: Dp, y: Dp): ViewPoint<T>
|
||||||
|
|
||||||
|
public abstract fun viewPointFor(rectangle: Rectangle<T>): ViewPoint<T>
|
||||||
|
|
||||||
|
// Selection rectangle. If null - no selection
|
||||||
|
public var selectRect: DpRect? by mutableStateOf(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public val DpRect.topLeft: DpOffset get() = DpOffset(left, top)
|
||||||
|
|
||||||
|
public val DpRect.bottomRight: DpOffset get() = DpOffset(right, bottom)
|
@ -13,7 +13,6 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import center.sciprog.maps.features.Feature.Companion.defaultZoomRange
|
import center.sciprog.maps.features.Feature.Companion.defaultZoomRange
|
||||||
import kotlin.math.floor
|
|
||||||
|
|
||||||
public typealias DoubleRange = ClosedFloatingPointRange<Double>
|
public typealias DoubleRange = ClosedFloatingPointRange<Double>
|
||||||
|
|
||||||
@ -60,11 +59,11 @@ public fun <T : Any> Iterable<Feature<T>>.computeBoundingBox(
|
|||||||
public class FeatureSelector<T : Any>(
|
public class FeatureSelector<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
override var attributes: AttributeMap = AttributeMap(),
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
public val selector: (zoom: Int) -> Feature<T>,
|
public val selector: (zoom: Double) -> Feature<T>,
|
||||||
) : Feature<T> {
|
) : Feature<T> {
|
||||||
override val zoomRange: ClosedFloatingPointRange<Double> get() = defaultZoomRange
|
override val zoomRange: ClosedFloatingPointRange<Double> get() = defaultZoomRange
|
||||||
|
|
||||||
override fun getBoundingBox(zoom: Double): Rectangle<T>? = selector(floor(zoom).toInt()).getBoundingBox(zoom)
|
override fun getBoundingBox(zoom: Double): Rectangle<T>? = selector(zoom).getBoundingBox(zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PathFeature<T : Any>(
|
public class PathFeature<T : Any>(
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
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.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
|
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||||
|
import androidx.compose.ui.graphics.drawscope.translate
|
||||||
|
import androidx.compose.ui.graphics.nativeCanvas
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.graphics.vector.VectorPainter
|
||||||
|
import org.jetbrains.skia.Font
|
||||||
|
import org.jetbrains.skia.Paint
|
||||||
|
import kotlin.math.PI
|
||||||
|
|
||||||
|
|
||||||
|
internal fun Color.toPaint(): Paint = Paint().apply {
|
||||||
|
isAntiAlias = true
|
||||||
|
color = toArgb()
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T : Any> DrawScope.drawFeature(
|
||||||
|
state: CoordinateViewState<T>,
|
||||||
|
painterCache: Map<VectorImageFeature<T>, VectorPainter>,
|
||||||
|
feature: Feature<T>,
|
||||||
|
): Unit = with(state) {
|
||||||
|
fun T.toOffset(): Offset = toOffset(this@drawFeature)
|
||||||
|
|
||||||
|
when (feature) {
|
||||||
|
is FeatureSelector -> drawFeature(state, painterCache, feature.selector(state.zoom))
|
||||||
|
is CircleFeature -> drawCircle(
|
||||||
|
feature.color,
|
||||||
|
feature.size.toPx(),
|
||||||
|
center = feature.center.toOffset()
|
||||||
|
)
|
||||||
|
|
||||||
|
is RectangleFeature -> drawRect(
|
||||||
|
feature.color,
|
||||||
|
topLeft = feature.center.toOffset() - Offset(
|
||||||
|
feature.size.width.toPx() / 2,
|
||||||
|
feature.size.height.toPx() / 2
|
||||||
|
),
|
||||||
|
size = feature.size.toSize()
|
||||||
|
)
|
||||||
|
|
||||||
|
is LineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
|
||||||
|
is ArcFeature -> {
|
||||||
|
val dpRect = feature.oval.toDpRect().toRect()
|
||||||
|
|
||||||
|
val size = Size(dpRect.width, dpRect.height)
|
||||||
|
|
||||||
|
drawArc(
|
||||||
|
color = feature.color,
|
||||||
|
startAngle = feature.startAngle / PI.toFloat() * 180f,
|
||||||
|
sweepAngle = feature.arcLength / PI.toFloat() * 180f,
|
||||||
|
useCenter = false,
|
||||||
|
topLeft = dpRect.topLeft,
|
||||||
|
size = size,
|
||||||
|
style = Stroke()
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
is BitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
|
||||||
|
|
||||||
|
is VectorImageFeature -> {
|
||||||
|
val offset = feature.position.toOffset()
|
||||||
|
val size = feature.size.toSize()
|
||||||
|
translate(offset.x - size.width / 2, offset.y - size.height / 2) {
|
||||||
|
with(painterCache[feature]!!) {
|
||||||
|
draw(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is TextFeature -> drawIntoCanvas { canvas ->
|
||||||
|
val offset = feature.position.toOffset()
|
||||||
|
canvas.nativeCanvas.drawString(
|
||||||
|
feature.text,
|
||||||
|
offset.x + 5,
|
||||||
|
offset.y - 5,
|
||||||
|
Font().apply(feature.fontConfig),
|
||||||
|
feature.color.toPaint()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is DrawFeature -> {
|
||||||
|
val offset = feature.position.toOffset()
|
||||||
|
translate(offset.x, offset.y) {
|
||||||
|
feature.drawFeature(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is FeatureGroup -> {
|
||||||
|
feature.children.values.forEach {
|
||||||
|
drawFeature(state, painterCache, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is PathFeature -> {
|
||||||
|
TODO("MapPathFeature not implemented")
|
||||||
|
// val offset = feature.rectangle.center.toOffset() - feature.targetRect.center
|
||||||
|
// translate(offset.x, offset.y) {
|
||||||
|
// sca
|
||||||
|
// drawPath(feature.path, brush = feature.brush, style = feature.style)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
is PointsFeature -> {
|
||||||
|
val points = feature.points.map { it.toOffset() }
|
||||||
|
drawPoints(
|
||||||
|
points = points,
|
||||||
|
color = feature.color,
|
||||||
|
strokeWidth = feature.stroke,
|
||||||
|
pointMode = feature.pointMode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
//logger.error { "Unrecognized feature type: ${feature::class}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,45 +12,6 @@ import kotlin.math.max
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
public abstract class CoordinateViewState<T : Any>(
|
|
||||||
public val config: ViewConfig<T>,
|
|
||||||
canvasSize: DpSize,
|
|
||||||
viewPoint: ViewPoint<T>,
|
|
||||||
) {
|
|
||||||
|
|
||||||
public abstract val space: CoordinateSpace<T>
|
|
||||||
|
|
||||||
public var canvasSize: DpSize by mutableStateOf(canvasSize)
|
|
||||||
protected var viewPointState: MutableState<ViewPoint<T>> = mutableStateOf(viewPoint)
|
|
||||||
|
|
||||||
public var viewPoint: ViewPoint<T>
|
|
||||||
get() = viewPointState.value
|
|
||||||
set(value) {
|
|
||||||
config.onViewChange(value)
|
|
||||||
viewPointState.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract fun DpOffset.toCoordinates(): T
|
|
||||||
|
|
||||||
public abstract fun T.toDpOffset(): DpOffset
|
|
||||||
|
|
||||||
public fun T.toOffset(density: Density): Offset = with(density){
|
|
||||||
val dpOffset = this@toOffset.toDpOffset()
|
|
||||||
Offset(dpOffset.x.toPx(), dpOffset.y.toPx())
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract fun ViewPoint<T>.moveBy(x: Dp, y: Dp): ViewPoint<T>
|
|
||||||
|
|
||||||
public abstract fun viewPointFor(rectangle: Rectangle<T>): ViewPoint<T>
|
|
||||||
|
|
||||||
// Selection rectangle. If null - no selection
|
|
||||||
public var selectRect: DpRect? by mutableStateOf<DpRect?>(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
public val DpRect.topLeft: DpOffset get() = DpOffset(left, top)
|
|
||||||
public val DpRect.bottomRight: DpOffset get() = DpOffset(right, bottom)
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
public fun <T : Any> Modifier.mapControls(
|
public fun <T : Any> Modifier.mapControls(
|
||||||
state: CoordinateViewState<T>,
|
state: CoordinateViewState<T>,
|
||||||
@ -64,7 +25,7 @@ public fun <T : Any> Modifier.mapControls(
|
|||||||
|
|
||||||
event.changes.forEach { change ->
|
event.changes.forEach { change ->
|
||||||
val dragStart = change.position
|
val dragStart = change.position
|
||||||
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
|
//val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
|
||||||
|
|
||||||
//start selection
|
//start selection
|
||||||
val selectionStart: Offset? =
|
val selectionStart: Offset? =
|
||||||
|
Loading…
Reference in New Issue
Block a user