From 340392bcf9a5d8fdacae3b5445de5d7912aaeaab Mon Sep 17 00:00:00 2001 From: MiniDay <372403923@qq.com> Date: Mon, 13 Jan 2025 23:11:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E7=89=88=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + README.md | 3 + build.gradle.kts | 80 +++++++++ gradle.properties | 18 ++ gradle/wrapper/gradle-wrapper.properties | 1 + settings.gradle.kts | 17 ++ .../compose/ui/client/ComposeScreen.kt | 154 ++++++++++++++++++ .../airgame/compose/ui/client/ComposeUIMod.kt | 57 +++++++ .../airgame/compose/ui/client/DebugTimer.kt | 10 ++ .../net/airgame/compose/ui/client/TestUI.kt | 33 ++++ .../resources/assets/compose-ui-mod/icon.png | Bin 0 -> 11786 bytes .../compose-ui-mod.client.mixins.json | 11 ++ .../net/airgame/compose/ui/ComposeUI.kt | 9 + src/main/resources/compose-ui-mod.mixins.json | 11 ++ src/main/resources/fabric.mod.json | 33 ++++ 15 files changed, 442 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 settings.gradle.kts create mode 100644 src/client/kotlin/net/airgame/compose/ui/client/ComposeScreen.kt create mode 100644 src/client/kotlin/net/airgame/compose/ui/client/ComposeUIMod.kt create mode 100644 src/client/kotlin/net/airgame/compose/ui/client/DebugTimer.kt create mode 100644 src/client/kotlin/net/airgame/compose/ui/client/TestUI.kt create mode 100644 src/client/resources/assets/compose-ui-mod/icon.png create mode 100644 src/client/resources/compose-ui-mod.client.mixins.json create mode 100644 src/main/kotlin/net/airgame/compose/ui/ComposeUI.kt create mode 100644 src/main/resources/compose-ui-mod.mixins.json create mode 100644 src/main/resources/fabric.mod.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e51cb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.gradle +.idea +.kotlin +build +run \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ae1a0a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ComposeUI + +将 Jetpack Compose 集成到 Minecraft 中 \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..af8b9f2 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,80 @@ +plugins { + kotlin("jvm") + id("maven-publish") + id("fabric-loom") version "1.9-SNAPSHOT" + + id("org.jetbrains.compose") + id("org.jetbrains.kotlin.plugin.compose") + + id("com.github.johnrengelman.shadow") version "8+" +} + +version = project.property("mod_version") as String +group = project.property("maven_group") as String + +base { + archivesName = project.property("archives_base_name") as String +} +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + withSourcesJar() +} +kotlin { + jvmToolchain(21) +} +loom { + splitEnvironmentSourceSets() + + mods { + register("compose-ui-mod") { + sourceSet("main") + sourceSet("client") + } + } + +} + +repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft("com.mojang:minecraft:${project.property("minecraft_version")}") + mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2") + modImplementation("net.fabricmc:fabric-loader:${project.property("loader_version")}") + modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("kotlin_loader_version")}") + + modImplementation("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_version")}") + + implementation(compose.desktop.currentOs) + includeInternal(compose.desktop.currentOs) { + exclude(module = "kotlin-stdlib") + exclude(module = "kotlin-stdlib-jdk7") + exclude(module = "kotlin-stdlib-jdk8") + exclude(module = "annotations") + } +} +tasks { + processResources { + filesMatching("fabric.mod.json") { + expand(project.properties) + } + } + runClient { + args("--username", "MiniDay", "--width", "1280", "--height", "720") + } +} +publishing { + publications { + create("mavenJava") { + artifactId = project.property("archives_base_name") as String + from(components["java"]) + } + } + repositories { + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..59d997e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx2G +# Fabric Properties +# check these on https://modmuss50.me/fabric.html +minecraft_version=1.21.4 +yarn_mappings=1.21.4+build.4 +loader_version=0.16.9 +kotlin_loader_version=1.13.0+kotlin.2.1.0 +# Mod Properties +mod_version=1.0.0 +maven_group=net.airgame +archives_base_name=compose-ui-mod +# Dependencies +# check this on https://modmuss50.me/fabric.html +fabric_version=0.114.0+1.21.4 + +kotlin.version=2.1.0 +compose.version=1.6.10 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ab6f03b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..5514ffc --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,17 @@ +pluginManagement { + repositories { + maven("https://maven.fabricmc.net/") { + name = "Fabric" + } + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + kotlin("jvm").version(extra["kotlin.version"] as String) + id("org.jetbrains.compose").version(extra["compose.version"] as String) + id("org.jetbrains.kotlin.plugin.compose").version(extra["kotlin.version"] as String) + } +} diff --git a/src/client/kotlin/net/airgame/compose/ui/client/ComposeScreen.kt b/src/client/kotlin/net/airgame/compose/ui/client/ComposeScreen.kt new file mode 100644 index 0000000..784c848 --- /dev/null +++ b/src/client/kotlin/net/airgame/compose/ui/client/ComposeScreen.kt @@ -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() + } +} \ No newline at end of file diff --git a/src/client/kotlin/net/airgame/compose/ui/client/ComposeUIMod.kt b/src/client/kotlin/net/airgame/compose/ui/client/ComposeUIMod.kt new file mode 100644 index 0000000..50e5f0c --- /dev/null +++ b/src/client/kotlin/net/airgame/compose/ui/client/ComposeUIMod.kt @@ -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) + } + }) + } +} diff --git a/src/client/kotlin/net/airgame/compose/ui/client/DebugTimer.kt b/src/client/kotlin/net/airgame/compose/ui/client/DebugTimer.kt new file mode 100644 index 0000000..3329e07 --- /dev/null +++ b/src/client/kotlin/net/airgame/compose/ui/client/DebugTimer.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/client/kotlin/net/airgame/compose/ui/client/TestUI.kt b/src/client/kotlin/net/airgame/compose/ui/client/TestUI.kt new file mode 100644 index 0000000..b14a6c1 --- /dev/null +++ b/src/client/kotlin/net/airgame/compose/ui/client/TestUI.kt @@ -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!") + } + } +} \ No newline at end of file diff --git a/src/client/resources/assets/compose-ui-mod/icon.png b/src/client/resources/assets/compose-ui-mod/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8ff89b7ff78d33e1e3c94f6b9b701694dd4202e6 GIT binary patch literal 11786 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 literal 0 HcmV?d00001 diff --git a/src/client/resources/compose-ui-mod.client.mixins.json b/src/client/resources/compose-ui-mod.client.mixins.json new file mode 100644 index 0000000..b4067e4 --- /dev/null +++ b/src/client/resources/compose-ui-mod.client.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.airgame.compose.ui.mixin.client", + "compatibilityLevel": "JAVA_21", + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/kotlin/net/airgame/compose/ui/ComposeUI.kt b/src/main/kotlin/net/airgame/compose/ui/ComposeUI.kt new file mode 100644 index 0000000..407135f --- /dev/null +++ b/src/main/kotlin/net/airgame/compose/ui/ComposeUI.kt @@ -0,0 +1,9 @@ +package net.airgame.compose.ui + +import net.fabricmc.api.ModInitializer + +class ComposeUI : ModInitializer { + + override fun onInitialize() { + } +} diff --git a/src/main/resources/compose-ui-mod.mixins.json b/src/main/resources/compose-ui-mod.mixins.json new file mode 100644 index 0000000..e91ad17 --- /dev/null +++ b/src/main/resources/compose-ui-mod.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.airgame.compose.ui.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..e8cc0cf --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,33 @@ +{ + "schemaVersion": 1, + "id": "compose-ui-mod", + "version": "${version}", + "name": "ComposeUI", + "description": "add jetpack compose to minecraft", + "authors": [], + "contact": {}, + "license": "MIT", + "icon": "assets/compose-ui-mod/icon.png", + "environment": "client", + "entrypoints": { + "client": [ + "net.airgame.compose.ui.client.ComposeUIMod" + ], + "main": [ + "net.airgame.compose.ui.ComposeUI" + ] + }, + "mixins": [ + "compose-ui-mod.mixins.json", + { + "config": "compose-ui-mod.client.mixins.json", + "environment": "client" + } + ], + "depends": { + "fabricloader": ">=${loader_version}", + "fabric-language-kotlin": ">=${kotlin_loader_version}", + "fabric": "*", + "minecraft": "${minecraft_version}" + } +}