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.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.PI
|
||||
import kotlin.random.Random
|
||||
@ -69,9 +68,9 @@ fun App() {
|
||||
)
|
||||
) {
|
||||
|
||||
geoJson(URL("https://raw.githubusercontent.com/ggolikov/cities-comparison/master/src/moscow.geo.json"))
|
||||
.attribute(ColorAttribute, Color.Blue)
|
||||
.attribute(AlphaAttribute, 0.4f)
|
||||
geoJson(javaClass.getResource("/moscow.geo.json")!!)
|
||||
.modifyAttribute(ColorAttribute, Color.Blue)
|
||||
.modifyAttribute(AlphaAttribute, 0.4f)
|
||||
|
||||
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(
|
||||
onClick = {_, click ->
|
||||
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,
|
||||
attrValue: T?,
|
||||
): Attributes = Attributes(
|
||||
@ -33,7 +33,7 @@ public fun <T, A : Attribute<T>> Attributes.attribute(
|
||||
/**
|
||||
* Add an element to a [SetAttribute]
|
||||
*/
|
||||
public fun <T, A : SetAttribute<T>> Attributes.addValue(
|
||||
public fun <T, A : SetAttribute<T>> Attributes.withAttributeElement(
|
||||
attribute: A,
|
||||
attrValue: T,
|
||||
): Attributes {
|
||||
@ -46,7 +46,7 @@ public fun <T, A : SetAttribute<T>> Attributes.addValue(
|
||||
/**
|
||||
* Remove an element from [SetAttribute]
|
||||
*/
|
||||
public fun <T, A : SetAttribute<T>> Attributes.removeValue(
|
||||
public fun <T, A : SetAttribute<T>> Attributes.withoutAttributeElement(
|
||||
attribute: A,
|
||||
attrValue: T,
|
||||
): Attributes {
|
||||
@ -56,8 +56,6 @@ public fun <T, A : SetAttribute<T>> Attributes.removeValue(
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
public fun <T : Any, A : Attribute<T>> Attributes(
|
||||
attribute: A,
|
||||
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? =
|
||||
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
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.attribute(key: Attribute<V>, value: V?): FeatureId<F> {
|
||||
feature(this, get(this).withAttributes { attribute(key, value) })
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.modifyAttribute(key: Attribute<V>, value: V?): FeatureId<F> {
|
||||
feature(this, get(this).withAttributes { withAttribute(key, value) })
|
||||
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
|
||||
*
|
||||
@ -101,10 +104,10 @@ public data class FeatureGroup<T : Any>(
|
||||
* TODO use context receiver for that
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun FeatureId<DraggableFeature<T>>.draggable(
|
||||
public fun <F : DraggableFeature<T>> FeatureId<F>.draggable(
|
||||
constraint: ((T) -> T)? = null,
|
||||
listener: (PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit)? = null,
|
||||
) {
|
||||
): FeatureId<F> {
|
||||
if (getAttribute(this, DraggableAttribute) == null) {
|
||||
val handle = DragHandle.withPrimaryButton<Any> { event, start, 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)
|
||||
}
|
||||
}
|
||||
this.attribute(DraggableAttribute, handle)
|
||||
modifyAttribute(DraggableAttribute, handle)
|
||||
}
|
||||
|
||||
//Apply callback
|
||||
if (listener != null) {
|
||||
onDrag(listener)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
@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,
|
||||
) {
|
||||
addAttribute(
|
||||
DragListenerAttribute,
|
||||
): FeatureId<F> = modifyAttributes {
|
||||
DragListenerAttribute.add(
|
||||
DragListener { event, from, to -> event.listener(from as ViewPoint<T>, to as ViewPoint<T>) }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <F : DomainFeature<T>> FeatureId<F>.onClick(
|
||||
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
addAttribute(
|
||||
ClickListenerAttribute,
|
||||
): FeatureId<F> = modifyAttributes {
|
||||
ClickListenerAttribute.add(
|
||||
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <F : DomainFeature<T>> FeatureId<F>.onHover(
|
||||
onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
addAttribute(
|
||||
HoverListenerAttribute,
|
||||
): FeatureId<F> = modifyAttributes {
|
||||
HoverListenerAttribute.add(
|
||||
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> =
|
||||
attribute(ColorAttribute, color)
|
||||
modifyAttribute(ColorAttribute, color)
|
||||
|
||||
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 {
|
||||
|
||||
|
@ -2,7 +2,6 @@ package center.sciprog.maps.features
|
||||
|
||||
import center.sciprog.attributes.Attributes
|
||||
import center.sciprog.attributes.ZAttribute
|
||||
import center.sciprog.attributes.attribute
|
||||
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.draggableLine(
|
||||
@ -20,7 +19,10 @@ public fun <T : Any> FeatureGroup<T>.draggableLine(
|
||||
get(bId).center,
|
||||
lineId?.id ?: id
|
||||
)
|
||||
if (attributes != null) currentId.modifyAttributes { attributes.attribute(ZAttribute, -10f) }
|
||||
currentId.modifyAttributes {
|
||||
ZAttribute(-10f)
|
||||
if (attributes != null) from(attributes)
|
||||
}
|
||||
lineId = 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,
|
||||
): FeatureId<Feature<Gmc>> {
|
||||
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 jsonGeometry = geoJsonGeometry(geometry, idOverride)
|
||||
return if( colorOverride!= null){
|
||||
|
@ -43,19 +43,22 @@ class XYViewScope(
|
||||
val bottomRight = rightBottom.toDpOffset()
|
||||
return DpRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
public fun rememberMapState(
|
||||
config: ViewConfig<XY>,
|
||||
initialViewPoint: ViewPoint<XY>? = null,
|
||||
initialRectangle: Rectangle<XY>? = null,
|
||||
): XYViewScope = remember {
|
||||
XYViewScope(config).also { mapState->
|
||||
if (initialViewPoint != null) {
|
||||
mapState.viewPoint = initialViewPoint
|
||||
} else if (initialRectangle != null) {
|
||||
mapState.viewPoint = mapState.computeViewPoint(initialRectangle)
|
||||
companion object{
|
||||
@Composable
|
||||
public fun remember(
|
||||
config: ViewConfig<XY>,
|
||||
initialViewPoint: ViewPoint<XY>? = null,
|
||||
initialRectangle: Rectangle<XY>? = null,
|
||||
): XYViewScope = remember {
|
||||
XYViewScope(config).also { mapState->
|
||||
if (initialViewPoint != null) {
|
||||
mapState.viewPoint = initialViewPoint
|
||||
} else if (initialRectangle != null) {
|
||||
mapState.viewPoint = mapState.computeViewPoint(initialRectangle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user