Introduce Attribute builders
This commit is contained in:
parent
69e7b058d2
commit
ffc77dc611
@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URL
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@ -69,9 +68,9 @@ fun App() {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
geoJson(URL("https://raw.githubusercontent.com/ggolikov/cities-comparison/master/src/moscow.geo.json"))
|
geoJson(javaClass.getResource("/moscow.geo.json")!!)
|
||||||
.attribute(ColorAttribute, Color.Blue)
|
.modifyAttribute(ColorAttribute, Color.Blue)
|
||||||
.attribute(AlphaAttribute, 0.4f)
|
.modifyAttribute(AlphaAttribute, 0.4f)
|
||||||
|
|
||||||
image(pointOne, Icons.Filled.Home)
|
image(pointOne, Icons.Filled.Home)
|
||||||
|
|
||||||
|
76302
demo/maps/src/jvmMain/resources/moscow.geo.json
Normal file
76302
demo/maps/src/jvmMain/resources/moscow.geo.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -79,7 +79,7 @@ fun App() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
val mapState: XYViewScope = rememberMapState(
|
val mapState: XYViewScope = XYViewScope.remember(
|
||||||
ViewConfig(
|
ViewConfig(
|
||||||
onClick = {_, click ->
|
onClick = {_, click ->
|
||||||
println("${click.focus.x}, ${click.focus.y}")
|
println("${click.focus.x}, ${click.focus.y}")
|
||||||
|
@ -19,7 +19,7 @@ public value class Attributes internal constructor(internal val map: Map<Attribu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T, A : Attribute<T>> Attributes.attribute(
|
public fun <T, A : Attribute<T>> Attributes.withAttribute(
|
||||||
attribute: A,
|
attribute: A,
|
||||||
attrValue: T?,
|
attrValue: T?,
|
||||||
): Attributes = Attributes(
|
): Attributes = Attributes(
|
||||||
@ -33,7 +33,7 @@ public fun <T, A : Attribute<T>> Attributes.attribute(
|
|||||||
/**
|
/**
|
||||||
* Add an element to a [SetAttribute]
|
* Add an element to a [SetAttribute]
|
||||||
*/
|
*/
|
||||||
public fun <T, A : SetAttribute<T>> Attributes.addValue(
|
public fun <T, A : SetAttribute<T>> Attributes.withAttributeElement(
|
||||||
attribute: A,
|
attribute: A,
|
||||||
attrValue: T,
|
attrValue: T,
|
||||||
): Attributes {
|
): Attributes {
|
||||||
@ -46,7 +46,7 @@ public fun <T, A : SetAttribute<T>> Attributes.addValue(
|
|||||||
/**
|
/**
|
||||||
* Remove an element from [SetAttribute]
|
* Remove an element from [SetAttribute]
|
||||||
*/
|
*/
|
||||||
public fun <T, A : SetAttribute<T>> Attributes.removeValue(
|
public fun <T, A : SetAttribute<T>> Attributes.withoutAttributeElement(
|
||||||
attribute: A,
|
attribute: A,
|
||||||
attrValue: T,
|
attrValue: T,
|
||||||
): Attributes {
|
): Attributes {
|
||||||
@ -56,8 +56,6 @@ public fun <T, A : SetAttribute<T>> Attributes.removeValue(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public fun <T : Any, A : Attribute<T>> Attributes(
|
public fun <T : Any, A : Attribute<T>> Attributes(
|
||||||
attribute: A,
|
attribute: A,
|
||||||
attrValue: T,
|
attrValue: T,
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package center.sciprog.attributes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A safe builder for [Attributes]
|
||||||
|
*/
|
||||||
|
public class AttributesBuilder internal constructor(private val map: MutableMap<Attribute<*>, Any> = mutableMapOf()) {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
public operator fun <T> get(attribute: Attribute<T>): T? = map[attribute] as? T
|
||||||
|
|
||||||
|
public operator fun <V> Attribute<V>.invoke(value: V?) {
|
||||||
|
if (value == null) {
|
||||||
|
map.remove(this)
|
||||||
|
} else {
|
||||||
|
map[this] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun from(attributes: Attributes) {
|
||||||
|
map.putAll(attributes.map)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <V> SetAttribute<V>.add(
|
||||||
|
attrValue: V,
|
||||||
|
) {
|
||||||
|
val currentSet: Set<V> = get(this) ?: emptySet()
|
||||||
|
map[this] = currentSet + attrValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an element from [SetAttribute]
|
||||||
|
*/
|
||||||
|
public fun <V> SetAttribute<V>.remove(
|
||||||
|
attrValue: V,
|
||||||
|
) {
|
||||||
|
val currentSet: Set<V> = get(this) ?: emptySet()
|
||||||
|
map[this] = currentSet - attrValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun build(): Attributes = Attributes(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun AttributesBuilder(
|
||||||
|
attributes: Attributes,
|
||||||
|
): AttributesBuilder = AttributesBuilder(attributes.map.toMutableMap())
|
@ -74,25 +74,28 @@ public data class FeatureGroup<T : Any>(
|
|||||||
public fun <A> getAttribute(id: FeatureId<Feature<T>>, key: Attribute<A>): A? =
|
public fun <A> getAttribute(id: FeatureId<Feature<T>>, key: Attribute<A>): A? =
|
||||||
get(id).attributes[key]
|
get(id).attributes[key]
|
||||||
|
|
||||||
public fun <F : Feature<T>> FeatureId<F>.modifyAttributes(modify: Attributes.() -> Attributes): FeatureId<F> {
|
|
||||||
feature(this, get(this).withAttributes(modify))
|
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
|
||||||
|
featureMap.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
||||||
|
|
||||||
|
public fun <F : Feature<T>> FeatureId<F>.modifyAttributes(modify: AttributesBuilder.() -> Unit): FeatureId<F> {
|
||||||
|
feature(
|
||||||
|
this,
|
||||||
|
get(this).withAttributes {
|
||||||
|
AttributesBuilder(this).apply(modify).build()
|
||||||
|
}
|
||||||
|
)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <F : Feature<T>, V> FeatureId<F>.attribute(key: Attribute<V>, value: V?): FeatureId<F> {
|
public fun <F : Feature<T>, V> FeatureId<F>.modifyAttribute(key: Attribute<V>, value: V?): FeatureId<F> {
|
||||||
feature(this, get(this).withAttributes { attribute(key, value) })
|
feature(this, get(this).withAttributes { withAttribute(key, value) })
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add multi-entry [SetAttribute] value
|
|
||||||
*/
|
|
||||||
public fun <F : Feature<T>, V> FeatureId<F>.addAttribute(key: SetAttribute<V>, value: V): FeatureId<F> {
|
|
||||||
feature(this, get(this).withAttributes { addValue(key, value) })
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add drag to this feature
|
* Add drag to this feature
|
||||||
*
|
*
|
||||||
@ -101,10 +104,10 @@ public data class FeatureGroup<T : Any>(
|
|||||||
* TODO use context receiver for that
|
* TODO use context receiver for that
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public fun FeatureId<DraggableFeature<T>>.draggable(
|
public fun <F : DraggableFeature<T>> FeatureId<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,
|
||||||
) {
|
): FeatureId<F> {
|
||||||
if (getAttribute(this, DraggableAttribute) == null) {
|
if (getAttribute(this, 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)
|
||||||
@ -121,70 +124,51 @@ public data class FeatureGroup<T : Any>(
|
|||||||
DragResult(end, true)
|
DragResult(end, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.attribute(DraggableAttribute, handle)
|
modifyAttribute(DraggableAttribute, handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Apply callback
|
//Apply callback
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
onDrag(listener)
|
onDrag(listener)
|
||||||
}
|
}
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public fun FeatureId<DraggableFeature<T>>.onDrag(
|
public fun <F : DraggableFeature<T>> FeatureId<F>.onDrag(
|
||||||
listener: PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit,
|
listener: PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit,
|
||||||
) {
|
): FeatureId<F> = modifyAttributes {
|
||||||
addAttribute(
|
DragListenerAttribute.add(
|
||||||
DragListenerAttribute,
|
|
||||||
DragListener { event, from, to -> event.listener(from as ViewPoint<T>, to as ViewPoint<T>) }
|
DragListener { event, from, to -> event.listener(from as ViewPoint<T>, to as ViewPoint<T>) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public fun <F : DomainFeature<T>> FeatureId<F>.onClick(
|
public fun <F : DomainFeature<T>> FeatureId<F>.onClick(
|
||||||
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
|
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
|
||||||
) {
|
): FeatureId<F> = modifyAttributes {
|
||||||
addAttribute(
|
ClickListenerAttribute.add(
|
||||||
ClickListenerAttribute,
|
|
||||||
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public fun <F : DomainFeature<T>> FeatureId<F>.onHover(
|
public fun <F : DomainFeature<T>> FeatureId<F>.onHover(
|
||||||
onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
|
onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
|
||||||
) {
|
): FeatureId<F> = modifyAttributes {
|
||||||
addAttribute(
|
HoverListenerAttribute.add(
|
||||||
HoverListenerAttribute,
|
|
||||||
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Cyclic update of a feature. Called infinitely until canceled.
|
|
||||||
// */
|
|
||||||
// public fun <F : Feature<T>> FeatureId<F>.updated(
|
|
||||||
// scope: CoroutineScope,
|
|
||||||
// update: suspend (F) -> F,
|
|
||||||
// ): Job = scope.launch {
|
|
||||||
// while (isActive) {
|
|
||||||
// feature(this@updated, update(get(this@updated)))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
public fun <F : Feature<T>> FeatureId<F>.color(color: Color): FeatureId<F> =
|
public fun <F : Feature<T>> FeatureId<F>.color(color: Color): FeatureId<F> =
|
||||||
attribute(ColorAttribute, color)
|
modifyAttribute(ColorAttribute, color)
|
||||||
|
|
||||||
public fun <F : Feature<T>> FeatureId<F>.zoomRange(range: FloatRange): FeatureId<F> =
|
public fun <F : Feature<T>> FeatureId<F>.zoomRange(range: FloatRange): FeatureId<F> =
|
||||||
attribute(ZoomRangeAttribute, range)
|
modifyAttribute(ZoomRangeAttribute, range)
|
||||||
|
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
|
|
||||||
featureMap.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = modify(attributes))
|
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package center.sciprog.maps.features
|
|||||||
|
|
||||||
import center.sciprog.attributes.Attributes
|
import center.sciprog.attributes.Attributes
|
||||||
import center.sciprog.attributes.ZAttribute
|
import center.sciprog.attributes.ZAttribute
|
||||||
import center.sciprog.attributes.attribute
|
|
||||||
|
|
||||||
|
|
||||||
public fun <T : Any> FeatureGroup<T>.draggableLine(
|
public fun <T : Any> FeatureGroup<T>.draggableLine(
|
||||||
@ -20,7 +19,10 @@ public fun <T : Any> FeatureGroup<T>.draggableLine(
|
|||||||
get(bId).center,
|
get(bId).center,
|
||||||
lineId?.id ?: id
|
lineId?.id ?: id
|
||||||
)
|
)
|
||||||
if (attributes != null) currentId.modifyAttributes { attributes.attribute(ZAttribute, -10f) }
|
currentId.modifyAttributes {
|
||||||
|
ZAttribute(-10f)
|
||||||
|
if (attributes != null) from(attributes)
|
||||||
|
}
|
||||||
lineId = currentId
|
lineId = currentId
|
||||||
return currentId
|
return currentId
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package center.sciprog.maps.geojson
|
||||||
|
|
||||||
|
import center.sciprog.attributes.Attribute
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
|
||||||
|
public object GeoJsonPropertiesAttribute : Attribute<JsonObject>
|
||||||
|
|
||||||
|
public object GeoJsonNameAttribute : Attribute<String>
|
@ -60,7 +60,7 @@ public fun FeatureGroup<Gmc>.geoJsonFeature(
|
|||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureId<Feature<Gmc>> {
|
): FeatureId<Feature<Gmc>> {
|
||||||
val geometry = geoJson.geometry ?: return group{}
|
val geometry = geoJson.geometry ?: return group{}
|
||||||
val idOverride = geoJson.properties?.get("id")?.jsonPrimitive?.contentOrNull ?: id
|
val idOverride = geoJson.json["id"]?.jsonPrimitive?.contentOrNull ?: geoJson.properties?.get("id")?.jsonPrimitive?.contentOrNull ?: id
|
||||||
val colorOverride = geoJson.properties?.get("color")?.jsonPrimitive?.intOrNull?.let { Color(it) }
|
val colorOverride = geoJson.properties?.get("color")?.jsonPrimitive?.intOrNull?.let { Color(it) }
|
||||||
val jsonGeometry = geoJsonGeometry(geometry, idOverride)
|
val jsonGeometry = geoJsonGeometry(geometry, idOverride)
|
||||||
return if( colorOverride!= null){
|
return if( colorOverride!= null){
|
||||||
|
@ -43,19 +43,22 @@ class XYViewScope(
|
|||||||
val bottomRight = rightBottom.toDpOffset()
|
val bottomRight = rightBottom.toDpOffset()
|
||||||
return DpRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)
|
return DpRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
companion object{
|
||||||
public fun rememberMapState(
|
@Composable
|
||||||
config: ViewConfig<XY>,
|
public fun remember(
|
||||||
initialViewPoint: ViewPoint<XY>? = null,
|
config: ViewConfig<XY>,
|
||||||
initialRectangle: Rectangle<XY>? = null,
|
initialViewPoint: ViewPoint<XY>? = null,
|
||||||
): XYViewScope = remember {
|
initialRectangle: Rectangle<XY>? = null,
|
||||||
XYViewScope(config).also { mapState->
|
): XYViewScope = remember {
|
||||||
if (initialViewPoint != null) {
|
XYViewScope(config).also { mapState->
|
||||||
mapState.viewPoint = initialViewPoint
|
if (initialViewPoint != null) {
|
||||||
} else if (initialRectangle != null) {
|
mapState.viewPoint = initialViewPoint
|
||||||
mapState.viewPoint = mapState.computeViewPoint(initialRectangle)
|
} else if (initialRectangle != null) {
|
||||||
|
mapState.viewPoint = mapState.computeViewPoint(initialRectangle)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user