From 0d15bc1d0b6cd5857a7273b2dd4852629efa06e5 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 3 Mar 2019 15:23:21 +0300 Subject: [PATCH] Minors fixes to Meta and JsonMetaFormat. Visualization moving forward --- .../hep/dataforge/meta/io/JsonMetaFormat.kt | 37 ++-- .../kotlin/hep/dataforge/meta/Laminate.kt | 2 +- .../kotlin/hep/dataforge/meta/Meta.kt | 3 +- .../vis/DisplayObjectPropertyListener.kt | 38 ++++ .../vis/spatial/FXSpatialRenderer.kt | 185 +++++++++++++++++- .../dataforge/vis/spatial/RendererDemoApp.kt | 54 +++-- .../kotlin/hep/dataforge/vis/spatial/Box.kt | 20 ++ .../dataforge/vis/spatial/DisplayObject3D.kt | 21 +- .../hep/dataforge/vis/spatial/Geometry.kt | 8 + .../kotlin/hep/dataforge/vis/DisplayObject.kt | 12 +- .../dataforge/vis/DisplayObjectDelegates.kt | 102 +++++++--- 11 files changed, 396 insertions(+), 86 deletions(-) create mode 100644 dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt create mode 100644 dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt create mode 100644 dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Geometry.kt diff --git a/dataforge-meta-io/src/commonMain/kotlin/hep/dataforge/meta/io/JsonMetaFormat.kt b/dataforge-meta-io/src/commonMain/kotlin/hep/dataforge/meta/io/JsonMetaFormat.kt index c9c67201..2c82c085 100644 --- a/dataforge-meta-io/src/commonMain/kotlin/hep/dataforge/meta/io/JsonMetaFormat.kt +++ b/dataforge-meta-io/src/commonMain/kotlin/hep/dataforge/meta/io/JsonMetaFormat.kt @@ -20,8 +20,13 @@ object JsonMetaFormat : MetaFormat { override fun read(input: Input): Meta { val str = input.readText() - val json = JsonTreeParser.parse(str) - return json.toMeta() + val json = Json.plain.parseJson(str) + + if(json is JsonObject) { + return json.toMeta() + } else { + TODO("non-object root") + } } } @@ -45,6 +50,20 @@ fun Meta.toJson(): JsonObject { return JsonObject(map) } + +fun JsonElement.toMetaItem() = when (this) { + is JsonPrimitive -> MetaItem.ValueItem(this.toValue()) + is JsonObject -> MetaItem.NodeItem(this.toMeta()) + is JsonArray -> { + if (this.all { it is JsonPrimitive }) { + val value = ListValue(this.map { (it as JsonPrimitive).toValue() }) + MetaItem.ValueItem(value) + } else { + TODO("mixed nodes json") + } + } +} + fun JsonObject.toMeta() = JsonMeta(this) private fun JsonPrimitive.toValue(): Value { @@ -57,19 +76,7 @@ private fun JsonPrimitive.toValue(): Value { class JsonMeta(val json: JsonObject) : Meta { override val items: Map> by lazy { json.mapKeys { NameToken(it.key) }.mapValues { entry -> - val element = entry.value - when (element) { - is JsonPrimitive -> MetaItem.ValueItem(element.toValue()) - is JsonObject -> MetaItem.NodeItem(element.toMeta()) - is JsonArray -> { - if (element.all { it is JsonPrimitive }) { - val value = ListValue(element.map { (it as JsonPrimitive).toValue() }) - MetaItem.ValueItem(value) - } else { - TODO("mixed nodes json") - } - } - } + entry.value.toMetaItem() } } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt index a1a62760..b16248ac 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt @@ -49,7 +49,7 @@ class Laminate(layers: List) : Meta { first().seal() all { it is MetaItem.NodeItem } -> { //list nodes in item - val nodes = map { it.node } + val nodes = map { (it as MetaItem.NodeItem).node } //represent as key->value entries val entries = nodes.flatMap { it.items.entries.asSequence() } //group by keys diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt index 2a9ef237..57affe76 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -174,8 +174,9 @@ val MetaItem<*>?.short get() = number?.toShort() val MetaItem<*>?.stringList get() = value?.list?.map { it.string } ?: emptyList() -val MetaItem.node: M +val MetaItem?.node: M? get() = when (this) { + null -> null is MetaItem.ValueItem -> error("Trying to interpret value meta item as node item") is MetaItem.NodeItem -> node } diff --git a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt b/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt new file mode 100644 index 00000000..63eb25c5 --- /dev/null +++ b/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt @@ -0,0 +1,38 @@ +package hep.dataforge.vis + +import hep.dataforge.meta.* +import hep.dataforge.names.Name +import hep.dataforge.names.toName +import javafx.beans.binding.ObjectBinding +import tornadofx.* + +class DisplayObjectPropertyListener(val obj: DisplayObject) { + private val binndings = HashMap?>>() + + init { + obj.onChange(this) { name, _, _ -> + binndings[name]?.invalidate() + } + } + + operator fun get(key: Name): ObjectBinding?> { + return binndings.getOrPut(key) { + object : ObjectBinding?>() { + override fun computeValue(): MetaItem<*>? = obj.getProperty(key) + } + } + } + + operator fun get(key: String) = get(key.toName()) +} + +fun ObjectBinding?>.value() = this.objectBinding { it.value } +fun ObjectBinding?>.string() = this.stringBinding { it.string } +fun ObjectBinding?>.number() = this.objectBinding { it.number } +fun ObjectBinding?>.double() = this.objectBinding { it.double } +fun ObjectBinding?>.float() = this.objectBinding { it.number?.toFloat() } +fun ObjectBinding?>.int() = this.objectBinding { it.int } +fun ObjectBinding?>.long() = this.objectBinding { it.long } +fun ObjectBinding?>.node() = this.objectBinding { it.node } + +fun ObjectBinding?>.transform(transform: (MetaItem<*>) -> T) = this.objectBinding { it?.let(transform) } diff --git a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/FXSpatialRenderer.kt b/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/FXSpatialRenderer.kt index 5cd9c140..829ec644 100644 --- a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/FXSpatialRenderer.kt +++ b/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/FXSpatialRenderer.kt @@ -3,24 +3,56 @@ package hep.dataforge.vis.spatial import hep.dataforge.context.Context import hep.dataforge.io.Output import hep.dataforge.meta.Meta +import hep.dataforge.meta.int +import hep.dataforge.vis.DisplayGroup +import hep.dataforge.vis.DisplayObject +import hep.dataforge.vis.DisplayObjectPropertyListener +import hep.dataforge.vis.transform +import javafx.event.EventHandler import javafx.scene.* +import javafx.scene.input.KeyCode +import javafx.scene.input.KeyEvent +import javafx.scene.input.MouseEvent +import javafx.scene.input.ScrollEvent import javafx.scene.paint.Color +import javafx.scene.paint.PhongMaterial import org.fxyz3d.geometry.Point3D import org.fxyz3d.shapes.primitives.CuboidMesh import org.fxyz3d.utils.CameraTransformer -class FXSpatialRenderer(override val context: Context) : Output { + +/** + * https://github.com/miho/JCSG for operations + * + * TODO move world and camera boilerplate to another class + */ +class FXSpatialRenderer(override val context: Context) : Output { private val world: Group = Group() - val camera = PerspectiveCamera() - - val cameraTransform = CameraTransformer().apply { - children.add(camera) + private val camera = PerspectiveCamera().apply { + nearClip = CAMERA_NEAR_CLIP + farClip = CAMERA_FAR_CLIP + translateZ = CAMERA_INITIAL_DISTANCE } + val cameraShift = CameraTransformer().apply { + val cameraFlip = CameraTransformer() + cameraFlip.children.add(camera) + cameraFlip.setRotateZ(180.0) + children.add(cameraFlip) + } + + val cameraRotation = CameraTransformer().apply { + children.add(cameraShift) + ry.angle = CAMERA_INITIAL_Y_ANGLE + rx.angle = CAMERA_INITIAL_X_ANGLE + rz.angle = CAMERA_INITIAL_Z_ANGLE + } + + val canvas: SubScene = SubScene( - Group(world, cameraTransform).apply { DepthTest.ENABLE }, + Group(world, cameraRotation).apply { DepthTest.ENABLE }, 1024.0, 768.0, true, @@ -29,17 +61,150 @@ class FXSpatialRenderer(override val context: Context) : Output fill = Color.GREY this.camera = this@FXSpatialRenderer.camera id = "canvas" + handleKeyboard(this) + handleMouse(this) } - private fun buildObject(obj: DisplayObject3D): Node { - val center = Point3D(obj.x.toFloat(), obj.y.toFloat(), obj.z.toFloat()) + private fun buildObject(obj: DisplayObject): Node { return when (obj) { - is Box3D -> CuboidMesh(obj.xSize, obj.ySize, obj.zSize).apply { this.center = center } + is DisplayGroup -> Group(obj.children.map { buildObject(it) }) + is Box -> CuboidMesh(obj.xSize, obj.ySize, obj.zSize).apply { + val listener = DisplayObjectPropertyListener(obj) + this.center = Point3D(obj.x.toFloat(), obj.y.toFloat(), obj.z.toFloat()) + this.diffuseColorProperty().bind(listener["color"].transform { + val int = it.int ?: 0 + val red = int and 0x00ff0000 shr 16 + val green = int and 0x0000ff00 shr 8 + val blue = int and 0x000000ff + return@transform Color.rgb(red, green, blue) + }) + } else -> TODO() } } - override fun render(obj: DisplayObject3D, meta: Meta) { + override fun render(obj: DisplayObject, meta: Meta) { world.children.add(buildObject(obj)) } + + private fun handleKeyboard(scene: SubScene) { + scene.onKeyPressed = EventHandler { event -> + if (event.isControlDown) { + when (event.code) { + KeyCode.Z -> { + cameraShift.t.x = 0.0 + cameraShift.t.y = 0.0 + camera.translateZ = CAMERA_INITIAL_DISTANCE + cameraRotation.ry.angle = CAMERA_INITIAL_Y_ANGLE + cameraRotation.rx.angle = CAMERA_INITIAL_X_ANGLE + } +// KeyCode.X -> axisGroup.isVisible = !axisGroup.isVisible +// KeyCode.S -> snapshot() +// KeyCode.DIGIT1 -> pixelMap.filterKeys { it.getLayerNumber() == 1 }.values.forEach { +// toggleTransparency( +// it +// ) +// } +// KeyCode.DIGIT2 -> pixelMap.filterKeys { it.getLayerNumber() == 2 }.values.forEach { +// toggleTransparency( +// it +// ) +// } +// KeyCode.DIGIT3 -> pixelMap.filterKeys { it.getLayerNumber() == 3 }.values.forEach { +// toggleTransparency( +// it +// ) +// } + else -> { + }//do nothing + } + } + } + } + + private fun handleMouse(scene: SubScene) { + + var mousePosX: Double = 0.0 + var mousePosY: Double = 0.0 + var mouseOldX: Double = 0.0 + var mouseOldY: Double = 0.0 + var mouseDeltaX: Double = 0.0 + var mouseDeltaY: Double = 0.0 + + scene.onMousePressed = EventHandler { me -> + mousePosX = me.sceneX + mousePosY = me.sceneY + mouseOldX = me.sceneX + mouseOldY = me.sceneY + } + + scene.onMouseDragged = EventHandler { me -> + mouseOldX = mousePosX + mouseOldY = mousePosY + mousePosX = me.sceneX + mousePosY = me.sceneY + mouseDeltaX = mousePosX - mouseOldX + mouseDeltaY = mousePosY - mouseOldY + + val modifier = when { + me.isControlDown -> CONTROL_MULTIPLIER + me.isShiftDown -> SHIFT_MULTIPLIER + else -> 1.0 + } + + if (me.isPrimaryButtonDown) { + cameraRotation.rz.angle = + cameraRotation.rz.angle + mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED + cameraRotation.rx.angle = + cameraRotation.rx.angle + mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED + // } else if (me.isSecondaryButtonDown()) { + // double z = camera.getTranslateZ(); + // double newZ = z + mouseDeltaX * MOUSE_SPEED * modifier*100; + // camera.setTranslateZ(newZ); + } else if (me.isSecondaryButtonDown) { + cameraShift.t.x = cameraShift.t.x + mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED + cameraShift.t.y = cameraShift.t.y + mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED + } + } + scene.onScroll = EventHandler { event -> + val z = camera.translateZ + val newZ = z + MOUSE_SPEED * event.deltaY * RESIZE_SPEED + camera.translateZ = newZ + } + } + + companion object { + private const val CAMERA_INITIAL_DISTANCE = -4500.0 + private const val CAMERA_INITIAL_X_ANGLE = -50.0 + private const val CAMERA_INITIAL_Y_ANGLE = 0.0 + private const val CAMERA_INITIAL_Z_ANGLE = -210.0 + private const val CAMERA_NEAR_CLIP = 0.1 + private const val CAMERA_FAR_CLIP = 10000.0 + private const val AXIS_LENGTH = 2000.0 + private const val CONTROL_MULTIPLIER = 0.1 + private const val SHIFT_MULTIPLIER = 10.0 + private const val MOUSE_SPEED = 0.1 + private const val ROTATION_SPEED = 2.0 + private const val TRACK_SPEED = 6.0 + private const val RESIZE_SPEED = 50.0 + private const val LINE_WIDTH = 3.0 + + private val redMaterial = PhongMaterial().apply { + diffuseColor = Color.DARKRED + specularColor = Color.RED + } + + private val whiteMaterial = PhongMaterial().apply { + diffuseColor = Color.WHITE + specularColor = Color.LIGHTBLUE + } + + private val greyMaterial = PhongMaterial().apply { + diffuseColor = Color.DARKGREY + specularColor = Color.GREY + } + + private val blueMaterial = PhongMaterial(Color.BLUE) + + } } \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt b/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt index 8ff1cc68..41cbb82f 100644 --- a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt +++ b/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt @@ -1,37 +1,57 @@ package hep.dataforge.vis.spatial import hep.dataforge.context.Global -import hep.dataforge.meta.EmptyMeta +import hep.dataforge.meta.number +import hep.dataforge.vis.DisplayGroup import javafx.scene.Parent +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import tornadofx.* +import kotlin.random.Random -class RendererDemoApp: App(RendererDemoView::class) +class RendererDemoApp : App(RendererDemoView::class) -class RendererDemoView: View(){ +class RendererDemoView : View() { val renderer = FXSpatialRenderer(Global) - override val root: Parent = borderpane{ + override val root: Parent = borderpane { center = renderer.canvas } + lateinit var group: DisplayGroup + init { - val cube = Box3D(null, EmptyMeta).apply { - xSize = 100.0 - ySize = 100.0 - zSize = 100.0 - } - renderer.render(cube) - renderer.camera.apply { - nearClip = 0.1 - farClip = 10000.0 - translateX = -200.0 - translateY = -200.0 - fieldOfView = 20.0 + renderer.render { + group = group { + box { + xSize = 100.0 + ySize = 100.0 + zSize = 100.0 + } + box { + x = 110.0 + xSize = 100.0 + ySize = 100.0 + zSize = 100.0 + } + } } - renderer.cameraTransform.apply{ + var color by group.properties.number(1530).int + + GlobalScope.launch { + val random = Random(111) + while (isActive) { + delay(1000) + color = random.nextInt(0, Int.MAX_VALUE) + } + } + + renderer.cameraRotation.apply { ry.angle = -30.0 rx.angle = -15.0 } diff --git a/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt new file mode 100644 index 00000000..5c0db659 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt @@ -0,0 +1,20 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.meta.EmptyMeta +import hep.dataforge.meta.Meta +import hep.dataforge.vis.DisplayGroup +import hep.dataforge.vis.DisplayObject +import hep.dataforge.vis.double + +class Box(parent: DisplayObject?, meta: Meta) : DisplayObject3D(parent, TYPE, meta) { + var xSize by double(1.0) + var ySize by double(1.0) + var zSize by double(1.0) + + companion object { + const val TYPE = "geometry.spatial.box" + } +} + +fun DisplayGroup.box(meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) = + Box(this, meta).apply(action).also { addChild(it) } \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/DisplayObject3D.kt b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/DisplayObject3D.kt index a1d81c81..be72fbd8 100644 --- a/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/DisplayObject3D.kt +++ b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/DisplayObject3D.kt @@ -1,9 +1,10 @@ package hep.dataforge.vis.spatial +import hep.dataforge.io.Output +import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta -import hep.dataforge.vis.DisplayLeaf -import hep.dataforge.vis.DisplayObject -import hep.dataforge.vis.double +import hep.dataforge.vis.* +import hep.dataforge.vis.DisplayObject.Companion.DEFAULT_TYPE open class DisplayObject3D(parent: DisplayObject?, type: String, meta: Meta) : DisplayLeaf(parent, type, meta) { @@ -16,13 +17,9 @@ open class DisplayObject3D(parent: DisplayObject?, type: String, meta: Meta) : D } } -class Box3D(parent: DisplayObject?, meta: Meta) : DisplayObject3D(parent, - TYPE, meta) { - var xSize by double(1.0) - var ySize by double(1.0) - var zSize by double(1.0) +fun DisplayGroup.group(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit = {}) = + DisplayNode(this, DEFAULT_TYPE, meta).apply(action).also{addChild(it)} - companion object { - const val TYPE = "geometry.spatial.box" - } -} + +fun Output.render(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit) = + render(DisplayNode(null, DEFAULT_TYPE, EmptyMeta).apply(action), meta) diff --git a/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Geometry.kt b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Geometry.kt new file mode 100644 index 00000000..53766a49 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Geometry.kt @@ -0,0 +1,8 @@ +package hep.dataforge.vis.spatial + +//TODO replace by platform optimized version +data class Point3D( + val x: Float, + val y: Float, + val z: Float +) \ No newline at end of file diff --git a/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObject.kt b/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObject.kt index 53d97074..cb9dd110 100644 --- a/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObject.kt +++ b/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObject.kt @@ -66,14 +66,18 @@ tailrec fun DisplayObject.getProperty(name: Name): MetaItem<*>? = properties[nam /** * A change listener for [DisplayObject] configuration. */ -fun DisplayObject.onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) = +fun DisplayObject.onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) { properties.style.onChange(owner, action) + parent?.onChange(owner, action) +} /** * Remove all meta listeners with matching owners */ -fun DisplayObject.removeChangeListener(owner: Any?) = +fun DisplayObject.removeChangeListener(owner: Any?) { properties.style.removeListener(owner) + parent?.removeChangeListener(owner) +} /** @@ -92,7 +96,7 @@ internal data class ObjectListener( * Basic group of display objects */ open class DisplayNode( - override val parent: DisplayObject?, + override val parent: DisplayObject? = null, override val type: String = DEFAULT_TYPE, meta: Meta = EmptyMeta ) : DisplayGroup { @@ -115,7 +119,7 @@ open class DisplayNode( } override fun removeChild(obj: DisplayObject) { - if(_children.remove(obj)){ + if (_children.remove(obj)) { listeners.forEach { it.action } } } diff --git a/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt b/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt index b3a2e042..d99ad547 100644 --- a/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt +++ b/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt @@ -1,48 +1,98 @@ package hep.dataforge.vis import hep.dataforge.meta.* -import hep.dataforge.values.Null +import hep.dataforge.names.Name +import hep.dataforge.names.toName import hep.dataforge.values.Value import kotlin.jvm.JvmName +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty -fun DisplayObject.value(default: Value = Null, key: String? = null) = - ValueConfigDelegate(properties, key, default) +/** + * A delegate for display object properties + */ +class DisplayObjectDelegate( + val key: Name?, + val default: MetaItem<*>?, + val inherited: Boolean +) : ReadWriteProperty?> { + override fun getValue(thisRef: DisplayObject, property: KProperty<*>): MetaItem<*>? { + val name = key ?: property.name.toName() + return if (inherited) { + thisRef.getProperty(name) + } else { + thisRef.properties[name] + } ?: default + } -fun DisplayObject.string(default: String? = null, key: String? = null) = - StringConfigDelegate(properties, key, default) + override fun setValue(thisRef: DisplayObject, property: KProperty<*>, value: MetaItem<*>?) { + val name = key ?: property.name.toName() + thisRef.properties.style[name] = value + } +} -fun DisplayObject.boolean(default: Boolean? = null, key: String? = null) = - BooleanConfigDelegate(properties, key, default) +class DisplayObjectDelegateWrapper( + val key: Name?, + val default: T, + val inherited: Boolean, + val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) }, + val read: (MetaItem<*>?) -> T? +) : ReadWriteProperty { + override fun getValue(thisRef: DisplayObject, property: KProperty<*>): T { + val name = key ?: property.name.toName() + return if (inherited) { + read(thisRef.getProperty(name)) + } else { + read(thisRef.properties[name]) + } ?: default + } -fun DisplayObject.number(default: Number? = null, key: String? = null) = - NumberConfigDelegate(properties, key, default) - -fun DisplayObject.double(default: Double? = null, key: String? = null) = - NumberConfigDelegate(properties, key, default).double - -fun DisplayObject.int(default: Int? = null, key: String? = null) = - NumberConfigDelegate(properties, key, default).int + override fun setValue(thisRef: DisplayObject, property: KProperty<*>, value: T) { + val name = key ?: property.name.toName() + thisRef.properties.style.write(name, value) + } +} -fun DisplayObject.node(key: String? = null) = StyledNodeDelegate(properties, key) +fun DisplayObject.value(default: Value? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.value } + +fun DisplayObject.string(default: String? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.string } + +fun DisplayObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.boolean } + +fun DisplayObject.number(default: Number? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.number } + +fun DisplayObject.double(default: Double? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.double } + +fun DisplayObject.int(default: Int? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.int } + + +fun DisplayObject.node(key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), null, inherited) { it.node } //fun Configurable.spec(spec: Specification, key: String? = null) = ChildConfigDelegate(key) { spec.wrap(this) } @JvmName("safeString") -fun DisplayObject.string(default: String, key: String? = null) = - SafeStringConfigDelegate(properties, key, default) +fun DisplayObject.string(default: String, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.string } @JvmName("safeBoolean") -fun DisplayObject.boolean(default: Boolean, key: String? = null) = - SafeBooleanConfigDelegate(properties, key, default) +fun DisplayObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.boolean } @JvmName("safeNumber") -fun DisplayObject.number(default: Number, key: String? = null) = - SafeNumberConfigDelegate(properties, key, default) +fun DisplayObject.number(default: Number, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.number } @JvmName("safeDouble") -fun DisplayObject.double(default: Double, key: String? = null) = - SafeNumberConfigDelegate(properties, key, default).double +fun DisplayObject.double(default: Double, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.double } -inline fun > DisplayObject.enum(default: E, key: String? = null) = - SafeEnumvConfigDelegate(properties, key, default) { enumValueOf(it) } \ No newline at end of file +inline fun > DisplayObject.enum(default: E, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { item -> item.string?.let { enumValueOf(it) } } \ No newline at end of file