feat: 初版提交
This commit is contained in:
154
src/client/kotlin/net/airgame/compose/ui/client/ComposeScreen.kt
Normal file
154
src/client/kotlin/net/airgame/compose/ui/client/ComposeScreen.kt
Normal file
@@ -0,0 +1,154 @@
|
||||
package net.airgame.compose.ui.client
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.InternalComposeUiApi
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Canvas
|
||||
import androidx.compose.ui.graphics.asComposeCanvas
|
||||
import androidx.compose.ui.input.pointer.PointerButton
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.scene.ComposeScene
|
||||
import androidx.compose.ui.scene.SingleLayerComposeScene
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import com.mojang.blaze3d.platform.TextureUtil
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import kotlinx.coroutines.launch
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.gui.screen.Screen
|
||||
import net.minecraft.client.texture.NativeImage
|
||||
import net.minecraft.text.Text
|
||||
import org.jetbrains.skia.Color
|
||||
import org.jetbrains.skia.EncodedImageFormat
|
||||
import org.jetbrains.skia.Surface
|
||||
import org.jetbrains.skiko.FrameDispatcher
|
||||
|
||||
@OptIn(InternalComposeUiApi::class)
|
||||
class ComposeScreen(
|
||||
val content: @Composable (ComposeScreen) -> Unit
|
||||
) : Screen(Text.literal("")) {
|
||||
private val frameDispatcher = FrameDispatcher(ComposeUIMod.singleThreadDispatcher) {
|
||||
renderFrame()
|
||||
}
|
||||
|
||||
private val windowWidth: Int get() = MinecraftClient.getInstance().window.width
|
||||
private val windowHeight: Int get() = MinecraftClient.getInstance().window.height
|
||||
|
||||
private lateinit var surface: Surface
|
||||
private lateinit var canvas :Canvas
|
||||
private lateinit var scene: ComposeScene
|
||||
|
||||
override fun init() {
|
||||
surface = Surface.makeRasterN32Premul(windowWidth, windowHeight)
|
||||
canvas = surface.canvas.asComposeCanvas()
|
||||
scene = SingleLayerComposeScene(
|
||||
coroutineContext = ComposeUIMod.singleThreadDispatcher,
|
||||
size = IntSize(windowWidth, windowHeight),
|
||||
density = Density(MinecraftClient.getInstance().options.guiScale.value.toFloat())
|
||||
) {
|
||||
frameDispatcher.scheduleFrame()
|
||||
}
|
||||
ComposeUIMod.coroutineScope.launch {
|
||||
scene.setContent {
|
||||
content(this@ComposeScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
if (surface.isClosed) {
|
||||
return
|
||||
}
|
||||
ComposeUIMod.coroutineScope.launch {
|
||||
scene.sendPointerEvent(
|
||||
PointerEventType.Move,
|
||||
position = Offset(
|
||||
// mouseX.toFloat(), mouseY.toFloat()
|
||||
MinecraftClient.getInstance().mouse.x.toFloat(),
|
||||
MinecraftClient.getInstance().mouse.y.toFloat()
|
||||
)
|
||||
)
|
||||
}
|
||||
context.draw {
|
||||
val vertexConsumer = it.getBuffer(ComposeUIMod.composeLayer)
|
||||
val matrix4f = context.matrices.peek().positionMatrix
|
||||
vertexConsumer.vertex(matrix4f, 0f, 0f, 0f).texture(0f, 0f)
|
||||
vertexConsumer.vertex(matrix4f, 0f, height.toFloat(), 0f).texture(0f, 1f)
|
||||
vertexConsumer.vertex(matrix4f, width.toFloat(), height.toFloat(), 0f).texture(1f, 1f)
|
||||
vertexConsumer.vertex(matrix4f, width.toFloat(), 0f, 0f).texture(1f, 0f)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderFrame() {
|
||||
if (surface.isClosed) {
|
||||
return
|
||||
}
|
||||
val timer = DebugTimer()
|
||||
surface.canvas.clear(Color.TRANSPARENT)
|
||||
timer.log("clear")
|
||||
scene.render(canvas, System.nanoTime())
|
||||
timer.log("render")
|
||||
surface.makeImageSnapshot().use { image ->
|
||||
timer.log("makeImageSnapshot")
|
||||
image.encodeToData(EncodedImageFormat.PNG)?.use { data ->
|
||||
timer.log("encodeToData")
|
||||
val nativeImage = NativeImage.read(data.bytes)
|
||||
timer.log("NativeImage.read")
|
||||
RenderSystem.recordRenderCall {
|
||||
timer.log("recordRenderCall")
|
||||
TextureUtil.prepareImage(ComposeUIMod.textureGlID, 0, nativeImage.width, nativeImage.height)
|
||||
timer.log("prepareImage")
|
||||
nativeImage.upload(0, 0, 0, 0, 0, nativeImage.width, nativeImage.height, true)
|
||||
timer.log("upload")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun resize(client: MinecraftClient, width: Int, height: Int) {
|
||||
super.resize(client, width, height)
|
||||
val old = surface
|
||||
surface = Surface.makeRasterN32Premul(windowWidth, windowHeight)
|
||||
canvas = surface.canvas.asComposeCanvas()
|
||||
old.close()
|
||||
scene.size = IntSize(windowWidth, windowHeight)
|
||||
scene.density = Density(client.options.guiScale.value.toFloat())
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
ComposeUIMod.coroutineScope.launch {
|
||||
scene.sendPointerEvent(
|
||||
PointerEventType.Press,
|
||||
button = PointerButton.Primary,
|
||||
position = Offset(
|
||||
// mouseX.toFloat(), mouseY.toFloat()
|
||||
MinecraftClient.getInstance().mouse.x.toFloat(),
|
||||
MinecraftClient.getInstance().mouse.y.toFloat()
|
||||
)
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
ComposeUIMod.coroutineScope.launch {
|
||||
scene.sendPointerEvent(
|
||||
PointerEventType.Release,
|
||||
button = PointerButton.Primary,
|
||||
position = Offset(
|
||||
// mouseX.toFloat(), mouseY.toFloat()
|
||||
MinecraftClient.getInstance().mouse.x.toFloat(),
|
||||
MinecraftClient.getInstance().mouse.y.toFloat()
|
||||
)
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
MinecraftClient.getInstance().setScreen(null)
|
||||
scene.close()
|
||||
surface.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package net.airgame.compose.ui.client
|
||||
|
||||
import com.mojang.blaze3d.platform.TextureUtil
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.fabricmc.api.ClientModInitializer
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
|
||||
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper
|
||||
import net.minecraft.client.option.KeyBinding
|
||||
import net.minecraft.client.render.RenderLayer
|
||||
import net.minecraft.client.render.RenderPhase
|
||||
import net.minecraft.client.render.VertexFormat
|
||||
import net.minecraft.client.render.VertexFormats
|
||||
import net.minecraft.client.util.InputUtil
|
||||
import org.jetbrains.skiko.MainUIDispatcher
|
||||
|
||||
class ComposeUIMod : ClientModInitializer {
|
||||
companion object {
|
||||
val singleThreadDispatcher =
|
||||
MainUIDispatcher.limitedParallelism(1) + CoroutineExceptionHandler { _, throwable ->
|
||||
throwable.printStackTrace()
|
||||
}
|
||||
val coroutineScope = CoroutineScope(singleThreadDispatcher)
|
||||
val textureGlID: Int by lazy { TextureUtil.generateTextureId() }
|
||||
val composeLayer: RenderLayer by lazy {
|
||||
RenderLayer.of(
|
||||
"compose",
|
||||
VertexFormats.POSITION_TEXTURE,
|
||||
VertexFormat.DrawMode.QUADS,
|
||||
786432,
|
||||
RenderLayer.MultiPhaseParameters.builder()
|
||||
.texture(RenderPhase.TextureBase({
|
||||
RenderSystem.setShaderTexture(0, textureGlID)
|
||||
}, {}))
|
||||
.program(RenderPhase.POSITION_TEXTURE_PROGRAM)
|
||||
.transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
|
||||
.depthTest(RenderPhase.LEQUAL_DEPTH_TEST)
|
||||
.build(false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val bind = KeyBinding("测试", InputUtil.GLFW_KEY_G, "测试")
|
||||
|
||||
override fun onInitializeClient() {
|
||||
KeyBindingHelper.registerKeyBinding(bind)
|
||||
ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { client ->
|
||||
while (bind.wasPressed()) {
|
||||
val screen = ComposeScreen {
|
||||
TestUI()
|
||||
}
|
||||
client.setScreen(screen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.airgame.compose.ui.client
|
||||
|
||||
class DebugTimer {
|
||||
var time = System.currentTimeMillis()
|
||||
fun log(string: String) {
|
||||
val now = System.currentTimeMillis()
|
||||
println("$string: ${now - time} ms")
|
||||
time = now
|
||||
}
|
||||
}
|
||||
33
src/client/kotlin/net/airgame/compose/ui/client/TestUI.kt
Normal file
33
src/client/kotlin/net/airgame/compose/ui/client/TestUI.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
package net.airgame.compose.ui.client
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun TestUI() {
|
||||
var show by remember { mutableStateOf(true) }
|
||||
Column(
|
||||
Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Button(onClick = {
|
||||
show = !show
|
||||
println("clicked")
|
||||
}) {
|
||||
Text("点我切换")
|
||||
}
|
||||
AnimatedVisibility(show) {
|
||||
Text("Hello Compose Minecraft!")
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/client/resources/assets/compose-ui-mod/icon.png
Normal file
BIN
src/client/resources/assets/compose-ui-mod/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
11
src/client/resources/compose-ui-mod.client.mixins.json
Normal file
11
src/client/resources/compose-ui-mod.client.mixins.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "net.airgame.compose.ui.mixin.client",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"client": [
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user