net.peanuuutz.fork.ui.scene.component.NativeWidgetAdapter.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fork-ui Show documentation
Show all versions of fork-ui Show documentation
Comprehensive API designed for Minecraft modders
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)
}
}
}