From 33ae82a39c0fbbe2dbb4c16332e65637f7b277cb Mon Sep 17 00:00:00 2001 From: MiniDay <372403923@qq.com> Date: Sun, 8 Feb 2026 04:24:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0compose=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E5=92=8Ckotlin=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 4 +- .../airgame/compose/ui/client/ComposeUIMod.kt | 26 ---- .../airgame/compose/ui/client/ComposeUiMod.kt | 44 ++++++ .../airgame/compose/ui/client/DebugTimer.kt | 5 +- .../compose/ui/client/screen/ComposeScreen.kt | 5 +- .../ui/client/screen/ComposeScreenV2.kt | 126 ++++++++++++++++++ .../compose/ui/{ComposeUI.kt => ComposeUi.kt} | 2 +- src/main/resources/fabric.mod.json | 4 +- 8 files changed, 180 insertions(+), 36 deletions(-) delete mode 100644 src/client/kotlin/net/airgame/compose/ui/client/ComposeUIMod.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/screen/ComposeScreenV2.kt rename src/main/kotlin/net/airgame/compose/ui/{ComposeUI.kt => ComposeUi.kt} (76%) diff --git a/gradle.properties b/gradle.properties index 59d997e..483686e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,5 +14,5 @@ archives_base_name=compose-ui-mod # 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 +kotlin.version=2.3.0 +compose.version=1.10.0 diff --git a/src/client/kotlin/net/airgame/compose/ui/client/ComposeUIMod.kt b/src/client/kotlin/net/airgame/compose/ui/client/ComposeUIMod.kt deleted file mode 100644 index 61d3ef5..0000000 --- a/src/client/kotlin/net/airgame/compose/ui/client/ComposeUIMod.kt +++ /dev/null @@ -1,26 +0,0 @@ -package net.airgame.compose.ui.client - -import net.airgame.compose.ui.client.screen.ComposeScreen -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.util.InputUtil - -class ComposeUIMod : ClientModInitializer { - private 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 { - CenterUI { - TestUI() - } - } - client.setScreen(screen) - } - }) - } -} 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..11f5679 --- /dev/null +++ b/src/client/kotlin/net/airgame/compose/ui/client/ComposeUiMod.kt @@ -0,0 +1,44 @@ +package net.airgame.compose.ui.client + +import net.airgame.compose.ui.client.screen.ComposeScreen +import net.airgame.compose.ui.client.screen.ComposeScreenV2 +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.util.InputUtil +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class ComposeUiMod : ClientModInitializer { + companion object { + val logger: Logger = LoggerFactory.getLogger("ComposeUiMod") + } + + private val keyV1 = KeyBinding("测试V1", InputUtil.GLFW_KEY_G, "测试") + private val keyV2 = KeyBinding("测试V2", InputUtil.GLFW_KEY_J, "测试") + + override fun onInitializeClient() { + KeyBindingHelper.registerKeyBinding(keyV1) + ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { client -> + if (keyV1.wasPressed()) { + val screen = ComposeScreen { + CenterUI { + TestUI() + } + } + client.setScreen(screen) + return@EndTick + } + if (keyV2.wasPressed()) { + val screen = ComposeScreenV2 { + CenterUI { + TestUI() + } + } + client.setScreen(screen) + return@EndTick + } + }) + } +} diff --git a/src/client/kotlin/net/airgame/compose/ui/client/DebugTimer.kt b/src/client/kotlin/net/airgame/compose/ui/client/DebugTimer.kt index 3329e07..92bebc0 100644 --- a/src/client/kotlin/net/airgame/compose/ui/client/DebugTimer.kt +++ b/src/client/kotlin/net/airgame/compose/ui/client/DebugTimer.kt @@ -2,9 +2,10 @@ package net.airgame.compose.ui.client class DebugTimer { var time = System.currentTimeMillis() - fun log(string: String) { + + fun log(message: String) { val now = System.currentTimeMillis() - println("$string: ${now - time} ms") + ComposeUiMod.logger.info("{}: {} ms", message, now - time) time = now } } \ No newline at end of file diff --git a/src/client/kotlin/net/airgame/compose/ui/client/screen/ComposeScreen.kt b/src/client/kotlin/net/airgame/compose/ui/client/screen/ComposeScreen.kt index 72c3b30..dd4a188 100644 --- a/src/client/kotlin/net/airgame/compose/ui/client/screen/ComposeScreen.kt +++ b/src/client/kotlin/net/airgame/compose/ui/client/screen/ComposeScreen.kt @@ -7,8 +7,8 @@ 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.CanvasLayersComposeScene import androidx.compose.ui.scene.ComposeScene -import androidx.compose.ui.scene.MultiLayerComposeScene import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import com.mojang.blaze3d.platform.TextureUtil @@ -74,7 +74,7 @@ class ComposeScreen( private val mcGuiScale = MinecraftClient.getInstance().options.guiScale.value.toFloat() private val fontScaleValue = 1f - private val scene: ComposeScene = MultiLayerComposeScene( + private val scene: ComposeScene = CanvasLayersComposeScene( density = if (mcGuiScale <= 0) { Density(1f, fontScaleValue) } else { @@ -209,7 +209,6 @@ class ComposeScreen( ): Boolean { val x = MinecraftClient.getInstance().mouse.x.toFloat() val y = MinecraftClient.getInstance().mouse.y.toFloat() - println("mouseScrolled: $x $y $horizontalAmount $verticalAmount") mainScope.launch { scene.sendPointerEvent( PointerEventType.Scroll, diff --git a/src/client/kotlin/net/airgame/compose/ui/client/screen/ComposeScreenV2.kt b/src/client/kotlin/net/airgame/compose/ui/client/screen/ComposeScreenV2.kt new file mode 100644 index 0000000..625290a --- /dev/null +++ b/src/client/kotlin/net/airgame/compose/ui/client/screen/ComposeScreenV2.kt @@ -0,0 +1,126 @@ +package net.airgame.compose.ui.client.screen + +import androidx.compose.runtime.Composable +import androidx.compose.ui.InternalComposeUiApi +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.PointerButton +import androidx.compose.ui.input.pointer.PointerEventType +import net.airgame.compose.ui.client.ComposeUiMod +import net.airgame.compose.ui.client.render.ComposeRender +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.text.Text + +@OptIn(InternalComposeUiApi::class) +class ComposeScreenV2( + val composeContent: @Composable (ComposeScreenV2) -> Unit +) : Screen(Text.literal("FrameRenderComposeScreen")) { + private val composeRender = ComposeRender() + + private val windowWidth: Int get() = MinecraftClient.getInstance().window.width + private val windowHeight: Int get() = MinecraftClient.getInstance().window.height + private val guiScale: Float + get() { + val scale = MinecraftClient.getInstance().options.guiScale.value.toFloat() + if (scale <= 0) return 1f + return scale / 2 + } + private val fontScale: Float get() = 1f + + override fun init() { + composeRender.init(windowWidth, windowHeight, guiScale, fontScale) { + composeContent(this) + } + ComposeUiMod.logger.info( + "init: width={} height={} guiScale={} fontScale={}", + windowWidth, windowHeight, guiScale, fontScale + ) + } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + context.draw { + val vertexConsumer = it.getBuffer(composeRender.output) + 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) + } + } + + override fun resize(client: MinecraftClient, width: Int, height: Int) { + this.width = width + this.height = height + composeRender.resize(windowWidth, windowHeight, guiScale, fontScale) + ComposeUiMod.logger.info( + "resize: width={} height={} guiScale={} fontScale={}", + windowWidth, windowHeight, guiScale, fontScale + ) + } + + private fun getMouseButton(button: Int) = when (button) { + 0 -> PointerButton.Primary + 1 -> PointerButton.Secondary + 2 -> PointerButton.Tertiary + 3 -> PointerButton.Back + 4 -> PointerButton.Forward + else -> PointerButton.Primary + } + + private fun getMousePosition() = Offset( + MinecraftClient.getInstance().mouse.x.toFloat(), + MinecraftClient.getInstance().mouse.y.toFloat() + ) + + override fun mouseMoved(mouseX: Double, mouseY: Double) { + composeRender.post { + it.sendPointerEvent( + PointerEventType.Move, + position = getMousePosition() + ) + } + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + composeRender.post { + it.sendPointerEvent( + PointerEventType.Press, + button = getMouseButton(button), + position = getMousePosition() + ) + } + return true + } + + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + composeRender.post { + it.sendPointerEvent( + PointerEventType.Release, + button = getMouseButton(button), + position = getMousePosition() + ) + } + return true + } + + override fun mouseScrolled( + mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double + ): Boolean { + composeRender.post { + it.sendPointerEvent( + PointerEventType.Scroll, + button = PointerButton.Tertiary, + position = getMousePosition(), + scrollDelta = Offset(horizontalAmount.toFloat(), -verticalAmount.toFloat()) + ) + } + return true + } + + override fun close() { + MinecraftClient.getInstance().setScreen(null) + composeRender.destroy() + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/airgame/compose/ui/ComposeUI.kt b/src/main/kotlin/net/airgame/compose/ui/ComposeUi.kt similarity index 76% rename from src/main/kotlin/net/airgame/compose/ui/ComposeUI.kt rename to src/main/kotlin/net/airgame/compose/ui/ComposeUi.kt index 407135f..fa9564d 100644 --- a/src/main/kotlin/net/airgame/compose/ui/ComposeUI.kt +++ b/src/main/kotlin/net/airgame/compose/ui/ComposeUi.kt @@ -2,7 +2,7 @@ package net.airgame.compose.ui import net.fabricmc.api.ModInitializer -class ComposeUI : ModInitializer { +class ComposeUi : ModInitializer { override fun onInitialize() { } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e8cc0cf..eb8025f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -11,10 +11,10 @@ "environment": "client", "entrypoints": { "client": [ - "net.airgame.compose.ui.client.ComposeUIMod" + "net.airgame.compose.ui.client.ComposeUiMod" ], "main": [ - "net.airgame.compose.ui.ComposeUI" + "net.airgame.compose.ui.ComposeUi" ] }, "mixins": [