All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.peanuuutz.fork.ui.scene.component.NativeWidgetAdapter.kt Maven / Gradle / Ivy

The newest version!
package net.peanuuutz.fork.ui.scene.component

import kotlinx.coroutines.isActive
import net.minecraft.client.gui.Drawable
import net.minecraft.client.gui.Element
import net.minecraft.client.util.math.MatrixStack
import net.peanuuutz.fork.ui.internal.util.currentMouseOffset
import net.peanuuutz.fork.ui.internal.util.currentMousePosition
import net.peanuuutz.fork.ui.scene.base.MatrixStackCanvas
import net.peanuuutz.fork.ui.scene.base.toNative
import net.peanuuutz.fork.ui.ui.context.focus.FocusMoveDirection.Next
import net.peanuuutz.fork.ui.ui.context.focus.FocusRequester
import net.peanuuutz.fork.ui.ui.context.pointer.MouseButton
import net.peanuuutz.fork.ui.ui.context.pointer.PointerEvent
import net.peanuuutz.fork.ui.ui.context.pointer.PointerMoveEvent
import net.peanuuutz.fork.ui.ui.context.pointer.PointerScrollEvent
import net.peanuuutz.fork.ui.ui.context.pointer.PointerTapEvent
import net.peanuuutz.fork.ui.ui.context.pointer.isUnconsumed
import net.peanuuutz.fork.ui.ui.layout.Constraints
import net.peanuuutz.fork.ui.ui.layout.EmptyPlacement
import net.peanuuutz.fork.ui.ui.layout.Measurable
import net.peanuuutz.fork.ui.ui.layout.MeasurePolicy
import net.peanuuutz.fork.ui.ui.layout.MeasureResult
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.ui.ui.modifier.draw.drawWithContent
import net.peanuuutz.fork.ui.ui.modifier.input.filter
import net.peanuuutz.fork.ui.ui.modifier.input.focusRequester
import net.peanuuutz.fork.ui.ui.modifier.input.focusTarget
import net.peanuuutz.fork.ui.ui.modifier.input.onFocusChanged
import net.peanuuutz.fork.ui.ui.modifier.input.onKeyOut
import net.peanuuutz.fork.ui.ui.modifier.input.pointerInput
import net.peanuuutz.fork.ui.ui.modifier.input.textInput
import net.peanuuutz.fork.ui.ui.modifier.layout.onPlaced
import net.peanuuutz.fork.ui.ui.modifier.layout.onRemeasurementAvailable
import net.peanuuutz.fork.ui.ui.node.LayoutNode
import net.peanuuutz.fork.ui.ui.node.RemeasurementCallback
import net.peanuuutz.fork.ui.ui.node.positionInWindow
import net.peanuuutz.fork.ui.ui.unit.toIntOffset
import net.peanuuutz.fork.util.minecraft.client

internal class NativeWidgetAdapter(val widget: W) {
    val layoutNode: LayoutNode = LayoutNode()

    private lateinit var matrices: MatrixStack

    private lateinit var focusRequester: FocusRequester

    private val modifier: Modifier

    init {
        var builder = Modifier.onPlaced { info ->
            val (x, y) = info.positionInWindow().toIntOffset()
            widget.x = x
            widget.y = y
        }
        if (widget is Drawable) {
            matrices = MatrixStack()
            builder = builder.drawWithContent {
                // We only check if this is the right Canvas type, but not using it,
                // because vanilla UI system doesn't have certain nested layout,
                // all widgets are rendered in a way that they are all on the same
                // level. However, MatrixStackCanvas.matrices DOES transform in
                // advance, so it's not capable to render vanilla widgets.
                if (canvas is MatrixStackCanvas) {
                    val pointerPosition = currentMousePosition
                    val pointerX = pointerPosition.x.toInt()
                    val pointerY = pointerPosition.y.toInt()
                    widget.render(
                        matrices,
                        pointerX,
                        pointerY,
                        client.lastFrameDuration
                    )
                }
            }
        }
        if (widget is RemeasurementCallback) {
            builder = builder.onRemeasurementAvailable(widget::onRemeasurementAvailable)
        }
        if (widget is Element) {
            focusRequester = FocusRequester()
            builder = builder
                .pointerInput {
                    receive {
                        while (listenerContext.isActive) {
                            val event = filter { it.isCaptured }
                            val pointerPosition = currentMousePosition
                            val pointerX = pointerPosition.x.toDouble()
                            val pointerY = pointerPosition.y.toDouble()
                            when (event) {
                                is PointerMoveEvent -> {
                                    if (event.button == MouseButton.Unknown) {
                                        widget.mouseMoved(
                                            pointerX,
                                            pointerY
                                        )
                                    } else if (event.isUnconsumed) {
                                        val pointerOffset = currentMouseOffset
                                        val pointerOffsetX = pointerOffset.x.toDouble()
                                        val pointerOffsetY = pointerOffset.y.toDouble()
                                        val consumed = widget.mouseDragged(
                                            pointerX,
                                            pointerY,
                                            event.button.code,
                                            pointerOffsetX,
                                            pointerOffsetY
                                        )
                                        if (consumed) {
                                            event.consume()
                                        }
                                    }
                                }
                                is PointerTapEvent -> {
                                    if (event.isPressed) {
                                        if (event.isUnconsumed) {
                                            val consumed = widget.mouseClicked(
                                                pointerX,
                                                pointerY,
                                                event.button.code
                                            )
                                            if (consumed) {
                                                event.consume()
                                            }
                                            if (widget.isFocused) {
                                                focusRequester.requestFocus()
                                            }
                                        }
                                    } else {
                                        widget.mouseReleased(
                                            pointerX,
                                            pointerY,
                                            event.button.code
                                        )
                                    }
                                }
                                is PointerScrollEvent -> {
                                    if (event.isUnconsumed) {
                                        val consumed = widget.mouseScrolled(
                                            pointerX,
                                            pointerY,
                                            event.amount.toDouble()
                                        )
                                        if (consumed) {
                                            event.consume()
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                .onFocusChanged { isFocused, direction ->
                    if (isFocused) {
                        val resolvedDirection = direction ?: Next
                        widget.getNavigationPath(resolvedDirection.toNative())?.setFocused(true)
                    } else {
                        widget.focusedPath?.setFocused(false)
                    }
                }
                .textInput { flow ->
                    flow.collect { event ->
                        widget.charTyped(
                            event.char,
                            event.modifier.mask
                        )
                    }
                }
                .focusRequester(focusRequester)
                .focusTarget()
                .onKeyOut { event ->
                    if (event.isPressed) {
                        widget.keyPressed(
                            event.key.code,
                            0, // Ignore
                            event.modifier.mask
                        )
                    } else {
                        widget.keyReleased(
                            event.key.code,
                            0, // Ignore
                            event.modifier.mask
                        )
                    }
                }
        }
        modifier = builder
    }

    private var measurePolicy: NativeWidgetMeasurePolicy = ErrorNativeWidgetMeasurePolicy
        set(value) {
            if (field == value) {
                return
            }
            field = value
            layoutNode.measurePolicy = MeasurePolicyAdapter(value)
        }

    fun updateModifier(provided: Modifier) {
        layoutNode.modifier = provided then modifier
    }

    fun updateMeasurePolicy(provided: NativeWidgetMeasurePolicy) {
        measurePolicy = provided
    }

    private inner class MeasurePolicyAdapter(
        val measurePolicy: NativeWidgetMeasurePolicy
    ) : MeasurePolicy {
        override fun measure(measurables: List, constraints: Constraints): MeasureResult {
            val size = measurePolicy.measure(widget, constraints)
            return MeasureResult(size.width, size.height, EmptyPlacement)
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy