[orx-panel] Add explicit contract checks for lambdas with callsInPlace

This commit is contained in:
Edwin Jakobs
2025-08-20 19:37:43 +02:00
parent 77e3db4068
commit aaefb9eaeb
3 changed files with 187 additions and 24 deletions

View File

@@ -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<StyleSheet> = 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"))

View File

@@ -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 <T : Element> Element.initElement(classes: Array<out String>, element: T, init: T.() -> Unit): Element {
@OptIn(ExperimentalContracts::class)
fun <T : Element> Element.initElement(classes: Array<out String>, 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 <reified T : TextElement> Element.textElement(classes: Array<out String>, 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 <reified T : TextElement> Element.textElement(classes: Array<out Stri
return te
}
fun Element.p(vararg classes: String, init: P.() -> 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)
}

View File

@@ -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<StyleSheet> {
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()
}