Skip to content

WindowPointer.kt

src/jvmMain/kotlin/afield/graphics/vulkan/util/WindowPointer.kt · 89 lines

package afield.graphics.vulkan.util

The pointer over a renderer window and the window's size, in window points, read once a frame.

import org.lwjgl.glfw.GLFW

class WindowPointer

The pointer and the window's size in points (the cursor's units, half the framebuffer's pixels on a Retina display), read on the window's poll, where GLFW allows it, and readable from any thread afterwards. A sketch that works in canvas pixels, origin top left, y down, uses width and height as its world and x, y as its mouse.

inside means the window is under the pointer (GLFW_HOVERED) and the pointer is within it, a browser's "over the canvas": a window that merely surrounds where the pointer is, behind another window, does not count.

onPoll runs on every poll after the fields are updated, for pointer logic that has to see every frame (a brush that paints along a drag). Registering a second pointer under the same name on a window replaces the first.

class WindowPointer(
    /** The window whose pointer and size are read. Registering adds a sequence to its poll map, so nothing is read until the window is polled. */
    val window: Window,
    /** The key this registration takes in the window's poll map; a second pointer under the same name on the same window replaces the first. */
    val name: String,
    /** Run on every poll, after the fields below are updated, for pointer logic that has to see each frame rather than sample whenever it happens to look. */
    var onPoll: (WindowPointer) -> Unit = {},
) {

var width: Float

The window's width in points; its framebuffer width until the first poll.

    @Volatile
    var width: Float = window.width.toFloat()
        private set

var height: Float

The window's height in points; its framebuffer height until the first poll.

    @Volatile
    var height: Float = window.height.toFloat()
        private set

var x: Float

The pointer, in points from the window's top-left corner.

    @Volatile
    var x: Float = 0f
        private set

var y: Float

The pointer's distance down from the window's top edge, in points.

    @Volatile
    var y: Float = 0f
        private set

var inside: Boolean

The window is under the pointer and the pointer is within it.

    @Volatile
    var inside: Boolean = false
        private set

var down: Boolean

The left button is held.

    @Volatile
    var down: Boolean = false
        private set

var polled: Boolean

Whether the window has been polled yet. Until it has, width and height are the framebuffer's pixels, twice the points on a Retina display, so a sketch that places things by the window's size waits for this.

    @Volatile
    var polled: Boolean = false
        private set

init

    init {
        window.onPoll[name] = sequence {
            while (true) {
                val size = window.size()
                width = size.w.toFloat().coerceAtLeast(1f)
                height = size.h.toFloat().coerceAtLeast(1f)
                val cursor = window.cursor
                x = cursor.x
                y = cursor.y
                val hovered = GLFW.glfwGetWindowAttrib(window.windowHandle, GLFW.GLFW_HOVERED) == GLFW.GLFW_TRUE
                inside = hovered && x >= 0 && y >= 0 && x <= size.w && y <= size.h
                down = window.isMouseButtonPressed(GLFW.GLFW_MOUSE_BUTTON_LEFT)
                polled = true
                onPoll(this@WindowPointer)
                yield(true)
            }
        }
    }
}