Skip to content

CustomMarking.kt

src/jvmMain/kotlin/afield/app/server/CustomMarking.kt · 108 lines

package afield.app.server

import afield.data.table.div
import afield.data.table.get
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.runtime.execution.plus
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem
import afield.ui.notation.custom
import afield.ui.visualization.menu

import afield.app.protocol.CustomMarkingEntry
import afield.audio.synth.*
import afield.document.graph.Box
import afield.document.graph.box
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.document.properties.Delegation
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap

Custom marking menus: Box.customMarking maps a direction and a label to a callback, and the frontend shows the entries as a radial menu on the box.

MarkingDirections names the directions and their angles. When the menu is shown the server registers each callback under an id and sends the frontend serialisable entries; choosing an entry runs its callback.

object MarkingDirections

Direction definitions for custom marking menus. Angles match buttons.js polarToRect which uses sin(angle) for x, cos(angle) for y: 0=South, 90=East, 180=North, 270=West (clockwise from south).

data class DirectionInfo(val angle: Double, val radius: Double)

val directions

    val directions = mapOf(
        // Inner ring (radius 1)
        "N" to DirectionInfo(180.0, 1.0),
        "NE" to DirectionInfo(135.0, 1.0),
        "E" to DirectionInfo(90.0, 1.0),
        "SE" to DirectionInfo(45.0, 1.0),
        "S" to DirectionInfo(0.0, 1.0),
        "SW" to DirectionInfo(315.0, 1.0),
        "W" to DirectionInfo(270.0, 1.0),
        "NW" to DirectionInfo(225.0, 1.0),
        // Outer ring (radius 2)
        "N2" to DirectionInfo(180.0, 2.0),
        "NE2" to DirectionInfo(135.0, 2.0),
        "E2" to DirectionInfo(90.0, 2.0),
        "SE2" to DirectionInfo(45.0, 2.0),
        "S2" to DirectionInfo(0.0, 2.0),
        "SW2" to DirectionInfo(315.0, 2.0),
        "W2" to DirectionInfo(270.0, 2.0),
        "NW2" to DirectionInfo(225.0, 2.0),
    )
}

var Box.customMarking by Delegation<MutableMap<Pair<String, String>, () -> Unit>>(defaultValue

Box property for custom marking menu entries. Keys are Pair(direction, label) — e.g. "N" to "Play" Values are callbacks to invoke when that direction is selected.

var Box.customMarking by Delegation<MutableMap<Pair<String, String>, () -> Unit>>(defaultValue = { mutableMapOf() })

Registry for custom marking callbacks (maps callbackId -> callback)

private val customMarkingCallbacks = ConcurrentHashMap<String, () -> Unit>()

internal fun registerCustomMarkingCallback(callbackId: String, callback: () -> Unit) {

internal fun registerCustomMarkingCallback(callbackId: String, callback: () -> Unit) {
    customMarkingCallbacks[callbackId] = callback
}

internal fun fireCustomMarkingCallback(callbackId: String): Boolean

internal fun fireCustomMarkingCallback(callbackId: String): Boolean {
    val callback = customMarkingCallbacks.remove(callbackId)
    if (callback != null) {
        callback()
        return true
    }
    return false
}

internal fun prepareCustomMarkingEntries(box: Box): List<CustomMarkingEntry>

Prepares custom marking entries for a box by registering callbacks and returning serializable entries for the frontend.

internal fun prepareCustomMarkingEntries(box: Box): List<CustomMarkingEntry> {
    val marking = box.customMarking
    if (marking.isEmpty()) return emptyList()

    return marking.mapNotNull { (key, callback) ->
        val (direction, label) = key
        val info = MarkingDirections.directions[direction] ?: return@mapNotNull null
        val callbackId = "cm-${box.id}-${UUID.randomUUID()}"
        registerCustomMarkingCallback(callbackId, callback)
        CustomMarkingEntry(
            direction = direction,
            label = label,
            callbackId = callbackId,
            radius = info.radius,
            angle = info.angle
        )
    }
}