commonMain.org.openrndr.Program.kt Maven / Gradle / Ivy
The newest version!
@file:Suppress("unused")
package org.openrndr
import org.openrndr.animatable.Animatable
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.Drawer
import org.openrndr.events.Event
import org.openrndr.internal.Driver
import org.openrndr.math.Vector2
import kotlin.jvm.JvmRecord
expect fun rootClassName(): String
enum class WindowEventType {
MOVED,
RESIZED,
FOCUSED,
UNFOCUSED,
MINIMIZED,
RESTORED,
CLOSED
}
/**
* window event message
*/
@JvmRecord
data class WindowEvent(val type: WindowEventType, val position: Vector2, val size: Vector2, val focused: Boolean)
/**
* window drop item event message
*/
@JvmRecord
data class DropEvent(val position: Vector2, val files: List)
/**
* program event type
*/
enum class ProgramEventType {
/**
* indicates the program has ended
*/
ENDED
}
/**
* program event message
*/
@JvmRecord
data class ProgramEvent(val type: ProgramEventType)
@JvmRecord
data class RequestAssetsEvent(val origin: Any, val program: Program)
@JvmRecord
data class ProduceAssetsEvent(val origin: Any, val program: Program, val assetMetadata: AssetMetadata)
@JvmRecord
data class AssetMetadata(
val programName: String,
val assetBaseName: String,
val assetProperties: Map
)
interface InputEvents {
val mouse: MouseEvents
val keyboard: KeyEvents
val pointers: Pointers
}
interface Clipboard {
var contents: String?
}
interface Clock {
val seconds: Double
}
interface Program : InputEvents, ExtensionHost, Clock {
/**
* A map that can be used to store arbitrary data, including functions
*/
var userProperties: MutableMap
var name: String
var width: Int
var height: Int
var isNested: Boolean
var drawer: Drawer
var driver: Driver
val dispatcher: Dispatcher
val window: Window
var application: Application
suspend fun setup()
fun drawImpl()
fun draw()
val produceAssets: Event
val requestAssets: Event
var assetMetadata: () -> AssetMetadata
var assetProperties: MutableMap
var clock: () -> Double
var ended: Event
var backgroundColor: ColorRGBa?
val frameCount: Int
val clipboard: ProgramImplementation.ApplicationClipboard
fun updateFrameSecondsFromClock()
}
interface Window {
var title: String
var size: Vector2
var contentScale: Double
var presentationMode: PresentationMode
var multisample: WindowMultisample
var resizable: Boolean
fun requestFocus()
fun requestDraw()
/**
* Window focused event, triggered when the window receives focus
*/
val focused: Event
/**
* Window focused event, triggered when the window loses focus
*/
val unfocused: Event
/**
* Window moved event
*/
val moved: Event
/**
* Window sized event
*/
val sized: Event
/**
* Window minimized event
*/
val minimized: Event
/**
* Window restored (from minimization) event
*/
val restored: Event
/**
* Window restored (from minimization) event
*/
val closed: Event
/**
* Drop event, triggered when a file is dropped on the window
*/
val drop: Event
/**
* Window position
*/
var position: Vector2
}
/**
* The Program class, this is where most user implementations start.
*/
open class ProgramImplementation(val suspend: Boolean = false) : Program {
override var width = 0
override var height = 0
override val program: Program by lazy { this }
override var userProperties: MutableMap = mutableMapOf()
override var name = rootClassName()
private val animator by lazy { Animatable() }
override lateinit var drawer: Drawer
override lateinit var driver: Driver
override lateinit var application: Application
/** This is checked at runtime to disallow nesting [extend] blocks. */
override var isNested: Boolean = false
/**
* background color that is used to clear the background every frame
*/
override var backgroundColor: ColorRGBa? = ColorRGBa.BLACK
override val dispatcher = Dispatcher()
/**
* program ended event
*
* The [ended] event is emitted when the program is ended by closing the application window
*/
override var ended = Event()
private var firstFrameTime = Double.POSITIVE_INFINITY
/**
* clock function. defaults to returning the application time.
*/
override var clock =
{ if (firstFrameTime == Double.POSITIVE_INFINITY || frameCount <= 0) 0.0 else (application.seconds - firstFrameTime) }
override var assetProperties = mutableMapOf()
override var assetMetadata = {
AssetMetadata(this.name, namedTimestamp(), assetProperties)
}
final override val requestAssets = Event()
final override val produceAssets = Event()
init {
requestAssets.listen {
produceAssets.trigger(
ProduceAssetsEvent(
it.origin, it.program,
assetMetadata()
)
)
}
}
private var frameSeconds = 0.0
private var deltaSeconds: Double = 0.0
private var lastSeconds: Double = -1.0
override var frameCount = 0
/**
* The number of [seconds] since program start, or the time from a custom [clock].
* value is updated at the beginning of the frame only.
*/
override val seconds: Double
get() = frameSeconds
/**
* The elapsed time since the last draw loop
*/
val deltaTime: Double
get() = deltaSeconds
inner class ApplicationClipboard : Clipboard {
override var contents: String?
get() {
return application.clipboardContents
}
set(value) {
application.clipboardContents = value
}
}
override val clipboard = ApplicationClipboard()
/**
* list of installed extensions
*/
override val extensions = mutableListOf()
get() {
if (field.isEmpty()) isNested = false
return field
}
/**
* install an [Extension]
* @param extension the [Extension] to install
*/
override fun extend(extension: T): T {
extensions.add(extension)
extension.setup(this)
return extension
}
/**
* install an [Extension] and configure it
* @param extension the [Extension] to install
* @param configure a configuration function to called with [extension] as its receiver
* @return the installed [Extension]
*/
override fun extend(extension: T, configure: T.() -> Unit): T {
extensions.add(extension)
extension.configure()
extension.setup(this)
return extension
}
/**
* install an extension function for the given [ExtensionStage]
*/
override fun extend(stage: ExtensionStage, userDraw: Program.() -> Unit) {
if (isNested) error("Cannot nest extend blocks within extend blocks")
val functionExtension = when (stage) {
ExtensionStage.SETUP ->
object : Extension {
override var enabled: Boolean = true
override fun setup(program: Program) {
program.isNested = true
program.userDraw()
}
}
ExtensionStage.BEFORE_DRAW ->
object : Extension {
override var enabled: Boolean = true
override fun beforeDraw(drawer: Drawer, program: Program) {
program.isNested = true
program.userDraw()
}
}
ExtensionStage.AFTER_DRAW ->
object : Extension {
override var enabled: Boolean = true
override fun afterDraw(drawer: Drawer, program: Program) {
program.isNested = true
program.userDraw()
}
}
}
extensions.add(functionExtension)
}
/**
* Simplified window interface
*/
inner class Window : org.openrndr.Window {
override var title: String
get() = application.windowTitle
set(value) {
application.windowTitle = value
}
override var size
get() = application.windowSize
set(value) {
application.windowSize = value
}
override var contentScale
get() = application.windowContentScale
set(value) {
application.windowContentScale = value
}
override var presentationMode: PresentationMode
get() = application.presentationMode
set(value) {
application.presentationMode = value
}
override var multisample: WindowMultisample
get() {
return application.windowMultisample
}
set(value) {
application.windowMultisample = value
}
override var resizable: Boolean
get() {
return application.windowResizable
}
set(value) {
application.windowResizable = value
}
override fun requestFocus() = application.requestFocus()
override fun requestDraw() = application.requestDraw()
/**
* Window focused event, triggered when the window receives focus
*/
override val focused = Event("window-focused", postpone = true)
/**
* Window focused event, triggered when the window loses focus
*/
override val unfocused = Event("window-unfocused", postpone = true)
/**
* Window moved event
*/
override val moved = Event("window-moved", postpone = true)
/**
* Window sized event
*/
override val sized = Event("window-sized", postpone = true)
/**
* Window minimized event
*/
override val minimized = Event("window-minimized", postpone = true)
/**
* Window restored (from minimization) event
*/
override val restored = Event("window-restored", postpone = true)
/**
* Window restored (from minimization) event
*/
override val closed = Event("window-closed", postpone = true)
/**
* Drop event, triggered when a file is dropped on the window
*/
override val drop = Event("window-drop", postpone = true)
/**
* Window position
*/
override var position: Vector2
get() = application.windowPosition
set(value) {
application.windowPosition = value
}
}
override val window = Window()
override val keyboard by lazy { Keyboard() }
override val mouse by lazy { ApplicationMouse(application = { application }) }
override val pointers by lazy { Pointers(application = { application }) }
/**
* This runs exactly once before the first call to draw()
*/
override suspend fun setup() {}
/**
* This is the draw call that is called by Application. It takes care of handling extensions.
*/
override fun drawImpl() {
if (frameCount == 0) {
firstFrameTime = application.seconds
}
animator.updateAnimation()
updateFrameSecondsFromClock()
if (lastSeconds == -1.0) lastSeconds = seconds
deltaSeconds = frameSeconds - lastSeconds
lastSeconds = frameSeconds
backgroundColor?.let {
drawer.clear(it)
}
extensions.filter { it.enabled }.forEach { it.beforeDraw(drawer, this) }
draw()
extensions.reversed().filter { it.enabled }.forEach { it.afterDraw(drawer, this) }
frameCount++
}
fun animate(animationFunction: Animatable.() -> Unit) {
animator.animationFunction()
}
/**
* This is the user facing draw call. It should be overridden by the user.
*/
override fun draw() {}
override fun updateFrameSecondsFromClock() {
frameSeconds = clock()
}
}
expect fun Program.namedTimestamp(extension: String = "", path: String? = null): String
© 2015 - 2025 Weber Informatics LLC | Privacy Policy