From aaefb9eaebe61fc2f6d29ce036140d1ead75e3f8 Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Wed, 20 Aug 2025 19:37:43 +0200 Subject: [PATCH] [orx-panel] Add explicit contract checks for lambdas with `callsInPlace` --- .../org/openrndr/panel/ControlManager.kt | 11 + .../openrndr/panel/elements/LayoutBuilder.kt | 194 +++++++++++++++--- .../org/openrndr/panel/style/StyleSheet.kt | 6 + 3 files changed, 187 insertions(+), 24 deletions(-) diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/ControlManager.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/ControlManager.kt index 883971c0..db2438d6 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/ControlManager.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/ControlManager.kt @@ -11,6 +11,9 @@ import org.openrndr.panel.layout.Layouter import org.openrndr.panel.style.* import org.openrndr.panel.style.Display import org.openrndr.shape.Rectangle +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract private val logger = KotlinLogging.logger {} @@ -493,7 +496,11 @@ class ControlManagerBuilder(val controlManager: ControlManager) { controlManager.layouter.styleSheets.addAll(styleSheets.flatMap { it.flatten() }) } + @OptIn(ExperimentalContracts::class) fun layout(init: Body.() -> Unit) { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } val body = Body(controlManager) body.init() controlManager.body = body @@ -517,10 +524,14 @@ fun ControlManager.layout(init: Body.() -> Unit) { this.body = body } +@OptIn(ExperimentalContracts::class) fun Program.controlManager( defaultStyles: List = defaultStyles(), builder: ControlManagerBuilder.() -> Unit ): ControlManager { + contract { + callsInPlace(builder, InvocationKind.EXACTLY_ONCE) + } val cm = ControlManager() cm.program = this cm.fontManager.register("default", resourceUrl("/fonts/Roboto-Regular.ttf")) diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/LayoutBuilder.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/LayoutBuilder.kt index dda5e28f..28d89bc0 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/LayoutBuilder.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/LayoutBuilder.kt @@ -2,25 +2,45 @@ package org.openrndr.panel.elements import org.openrndr.draw.Drawer import org.openrndr.panel.ControlManager +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +@OptIn(ExperimentalContracts::class) fun Element.layout(init: Element.() -> Unit) { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } init() } +@OptIn(ExperimentalContracts::class) fun layout(controlManager: ControlManager, init: Body.() -> Unit): Body { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } val body = Body(controlManager) body.init() return body } -fun Element.initElement(classes: Array, element: T, init: T.() -> Unit): Element { +@OptIn(ExperimentalContracts::class) +fun Element.initElement(classes: Array, element: T, init: T.() -> Unit): T { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + append(element) element.classes.addAll(classes.map { ElementClass(it) }) element.init() return element } +@OptIn(ExperimentalContracts::class) fun Element.button(vararg classes: String, label: String = "button", init: Button.() -> Unit): Button { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } val button = Button().apply { this.classes.addAll(classes.map { ElementClass(it) }) this.id = id @@ -34,19 +54,56 @@ fun Button.clicked(listener: (Button.ButtonEvent) -> Unit) { events.clicked.listen(listener) } -fun Element.slider(vararg classes: String, init: Slider.() -> Unit) = initElement(classes, Slider(), init) as Slider -fun Element.toggle(vararg classes: String, init: Toggle.() -> Unit) = initElement(classes, Toggle(), init) as Toggle +@OptIn(ExperimentalContracts::class) +fun Element.slider(vararg classes: String, init: Slider.() -> Unit) : Slider { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, Slider(), init) as Slider +} -fun Element.colorpicker(vararg classes: String, init: Colorpicker.() -> Unit) = initElement(classes, Colorpicker(), init) -fun Element.colorpickerButton(vararg classes: String, init: ColorpickerButton.() -> Unit) = initElement(classes, ColorpickerButton(), init) +@OptIn(ExperimentalContracts::class) +fun Element.toggle(vararg classes: String, init: Toggle.() -> Unit): Toggle { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, Toggle(), init) as Toggle +} -fun Element.xyPad(vararg classes: String, init: XYPad.() -> Unit) = initElement(classes, XYPad(), init) as XYPad +@OptIn(ExperimentalContracts::class) +fun Element.colorpicker(vararg classes: String, init: Colorpicker.() -> Unit): Colorpicker { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, Colorpicker(), init) +} + +@OptIn(ExperimentalContracts::class) +fun Element.colorpickerButton(vararg classes: String, init: ColorpickerButton.() -> Unit): ColorpickerButton { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, ColorpickerButton(), init) +} + + +@OptIn(ExperimentalContracts::class) +fun Element.xyPad(vararg classes: String, init: XYPad.() -> Unit): XYPad { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, XYPad(), init) as XYPad +} fun Canvas.draw(f: (Drawer) -> Unit) { this.userDraw = f } -fun Element.canvas(vararg classes: String, init: Canvas.() -> Unit) : Canvas { +@OptIn(ExperimentalContracts::class) +fun Element.canvas(vararg classes: String, init: Canvas.() -> Unit): Canvas { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } val canvas = Canvas() classes.forEach { canvas.classes.add(ElementClass(it)) } canvas.init() @@ -54,40 +111,104 @@ fun Element.canvas(vararg classes: String, init: Canvas.() -> Unit) : Canvas { return canvas } -fun Element.dropdownButton(vararg classes: String, id: String? = null, label: String = "button", init: DropdownButton.() -> Unit) = initElement(classes, DropdownButton().apply { - this.id = id - this.label = label -}, init) +@OptIn(ExperimentalContracts::class) +fun Element.dropdownButton( + vararg classes: String, + id: String? = null, + label: String = "button", + init: DropdownButton.() -> Unit +): DropdownButton { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, DropdownButton().apply { + this.id = id + this.label = label + }, init) +} -fun Element.envelopeButton(vararg classes: String, init: EnvelopeButton.() -> Unit) = initElement(classes, EnvelopeButton().apply {}, init) -fun Element.envelopeEditor(vararg classes: String, init: EnvelopeEditor.() -> Unit) = initElement(classes, EnvelopeEditor().apply {}, init) +@OptIn(ExperimentalContracts::class) +fun Element.envelopeButton(vararg classes: String, init: EnvelopeButton.() -> Unit): EnvelopeButton { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, EnvelopeButton().apply {}, init) +} -fun Element.sequenceEditor(vararg classes: String, init: SequenceEditor.() -> Unit) = initElement(classes, SequenceEditor().apply {}, init) +@OptIn(ExperimentalContracts::class) +fun Element.envelopeEditor(vararg classes: String, init: EnvelopeEditor.() -> Unit): EnvelopeEditor { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, EnvelopeEditor().apply {}, init) +} -fun Element.slidersVector2(vararg classes: String, init: SlidersVector2.() -> Unit) = initElement(classes, SlidersVector2().apply {}, init) -fun Element.slidersVector3(vararg classes: String, init: SlidersVector3.() -> Unit) = initElement(classes, SlidersVector3().apply {}, init) -fun Element.slidersVector4(vararg classes: String, init: SlidersVector4.() -> Unit) = initElement(classes, SlidersVector4().apply {}, init) +@OptIn(ExperimentalContracts::class) +fun Element.sequenceEditor(vararg classes: String, init: SequenceEditor.() -> Unit): SequenceEditor { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, SequenceEditor().apply {}, init) +} +@OptIn(ExperimentalContracts::class) +fun Element.slidersVector2(vararg classes: String, init: SlidersVector2.() -> Unit): SlidersVector2 { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, SlidersVector2().apply {}, init) +} +@OptIn(ExperimentalContracts::class) +fun Element.slidersVector3(vararg classes: String, init: SlidersVector3.() -> Unit): SlidersVector3 { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, SlidersVector3().apply {}, init) +} + +@OptIn(ExperimentalContracts::class) +fun Element.slidersVector4(vararg classes: String, init: SlidersVector4.() -> Unit): SlidersVector4 { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, SlidersVector4().apply {}, init) +} -fun Element.textfield(vararg classes: String, init: Textfield.() -> Unit) = initElement(classes, Textfield(), init) +@OptIn(ExperimentalContracts::class) +fun Element.textfield(vararg classes: String, init: Textfield.() -> Unit): Textfield { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return initElement(classes, Textfield(), init) +} +@OptIn(ExperimentalContracts::class) fun DropdownButton.item(init: Item.() -> Unit): Item { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } val item = Item().apply(init) - - append(item) return item } +@OptIn(ExperimentalContracts::class) fun Element.div(vararg classes: String, init: Div.() -> Unit): Div { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } val div = Div() initElement(classes, div, init) return div } +@OptIn(ExperimentalContracts::class) inline fun Element.textElement(classes: Array, init: T.() -> String): T { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } @Suppress("DEPRECATION") val te = T::class.java.newInstance() te.classes.addAll(classes.map { ElementClass(it) }) te.text(te.init()) @@ -95,8 +216,33 @@ inline fun Element.textElement(classes: Array String): P = textElement(classes, init) -fun Element.h1(vararg classes: String, init: H1.() -> String): H1 = textElement(classes, init) -fun Element.h2(vararg classes: String, init: H2.() -> String): H2 = textElement(classes, init) -fun Element.h3(vararg classes: String, init: H3.() -> String): H3 = textElement(classes, init) +@OptIn(ExperimentalContracts::class) +fun Element.p(vararg classes: String, init: P.() -> String): P { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return textElement(classes, init) +} +@OptIn(ExperimentalContracts::class) +fun Element.h1(vararg classes: String, init: H1.() -> String): H1 { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return textElement(classes, init) +} +@OptIn(ExperimentalContracts::class) +fun Element.h2(vararg classes: String, init: H2.() -> String): H2 { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return textElement(classes, init) +} + +@OptIn(ExperimentalContracts::class) +fun Element.h3(vararg classes: String, init: H3.() -> String): H3 { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + return textElement(classes, init) +} diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/style/StyleSheet.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/style/StyleSheet.kt index 612c3205..aebd4b0b 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/style/StyleSheet.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/style/StyleSheet.kt @@ -4,6 +4,8 @@ import org.openrndr.color.ColorRGBa import org.openrndr.panel.style.PropertyInheritance.INHERIT import org.openrndr.panel.style.PropertyInheritance.RESET import java.util.* +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract import kotlin.reflect.KProperty enum class PropertyInheritance { @@ -232,7 +234,11 @@ fun StyleSheet.flatten(): List { return listOf(this) + children.flatMap { it.flatten() } } +@OptIn(ExperimentalContracts::class) fun styleSheet(selector: CompoundSelector = CompoundSelector.DUMMY, init: StyleSheet.() -> Unit): StyleSheet { + contract { + callsInPlace(init, kotlin.contracts.InvocationKind.EXACTLY_ONCE) + } return StyleSheet(selector).apply { init() }