diff --git a/build.gradle.kts b/build.gradle.kts index d156ed0..41cde4e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -36,6 +36,7 @@ loom { } repositories { + maven("https://maven.aliyun.com/repository/public") mavenLocal() mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") diff --git a/settings.gradle.kts b/settings.gradle.kts index 86e48c4..aa868d9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,6 @@ pluginManagement { repositories { + maven("https://maven.aliyun.com/repository/public") mavenLocal() maven("https://maven.fabricmc.net/") { name = "Fabric" diff --git a/src/client/kotlin/net/airgame/compose/ui/client/ComposeUiMod.kt b/src/client/kotlin/net/airgame/compose/ui/client/ComposeUiMod.kt index 11f5679..79bc4f7 100644 --- a/src/client/kotlin/net/airgame/compose/ui/client/ComposeUiMod.kt +++ b/src/client/kotlin/net/airgame/compose/ui/client/ComposeUiMod.kt @@ -1,7 +1,6 @@ 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 @@ -15,13 +14,12 @@ class ComposeUiMod : ClientModInitializer { val logger: Logger = LoggerFactory.getLogger("ComposeUiMod") } - private val keyV1 = KeyBinding("测试V1", InputUtil.GLFW_KEY_G, "测试") - private val keyV2 = KeyBinding("测试V2", InputUtil.GLFW_KEY_J, "测试") + private val key = KeyBinding("测试", InputUtil.GLFW_KEY_G, "测试") override fun onInitializeClient() { - KeyBindingHelper.registerKeyBinding(keyV1) + KeyBindingHelper.registerKeyBinding(key) ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { client -> - if (keyV1.wasPressed()) { + if (key.wasPressed()) { val screen = ComposeScreen { CenterUI { TestUI() @@ -30,15 +28,6 @@ class ComposeUiMod : ClientModInitializer { 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/render/ComposeRender.kt b/src/client/kotlin/net/airgame/compose/ui/client/render/ComposeRender.kt index e35ba82..9b10576 100644 --- a/src/client/kotlin/net/airgame/compose/ui/client/render/ComposeRender.kt +++ b/src/client/kotlin/net/airgame/compose/ui/client/render/ComposeRender.kt @@ -1,27 +1,28 @@ package net.airgame.compose.ui.client.render import androidx.compose.runtime.Composable +import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.ui.InternalComposeUiApi import androidx.compose.ui.graphics.Canvas import androidx.compose.ui.graphics.asComposeCanvas +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 kotlinx.coroutines.* import net.minecraft.client.render.RenderLayer import org.jetbrains.skiko.FrameDispatcher +import java.util.concurrent.Executors import java.util.function.Consumer @OptIn(InternalComposeUiApi::class) class ComposeRender { - // private val threadPool = Executors.newSingleThreadExecutor() - // private val composeScope = CoroutineScope(SupervisorJob() + threadPool.asCoroutineDispatcher()) - private val composeScope = MainScope() + private val threadPool = Executors.newSingleThreadExecutor { Thread(it, "ComposeRenderThread") } + private val composeScope = CoroutineScope(SupervisorJob() + threadPool.asCoroutineDispatcher()) private val frameDispatcher = FrameDispatcher(composeScope) { render() } private val skiaRender = SkiaRender() - private val composeScene: ComposeScene = MultiLayerComposeScene( + private val composeScene: ComposeScene = CanvasLayersComposeScene( coroutineContext = composeScope.coroutineContext, invalidate = { frameDispatcher.scheduleFrame() } ) @@ -29,29 +30,27 @@ class ComposeRender { val output: RenderLayer get() = skiaRender.output - data class ResizeMessage(val width: Int, val height: Int, val uiScale: Float, val fontScale: Float) + private data class ResizeMessage(val width: Int, val height: Int, val uiScale: Float, val fontScale: Float) // resize防抖:消息的容量为1,后续多余的resize请求会覆盖旧的resize请求 // compose 渲染线程定期消费 resize 消息 @Volatile - var resizeMessage: ResizeMessage? = null + private var resizeMessage: ResizeMessage? = null fun init(width: Int, height: Int, uiScale: Float, fontScale: Float, content: @Composable () -> Unit) { - skiaRender.init(width, height) + skiaRender.prepareTexture() val task = composeScope.async { - // 确保线程中的OpenGL状态机已经切换到目标窗口 - skiaRender.switchWindow() + // 确保渲染线程中的OpenGL状态机已经准备完毕 + skiaRender.prepareRender(width, height) composeCanvas = skiaRender.skiaSurface.canvas.asComposeCanvas() } runBlocking { // 确保 SkiaRender 完全初始化之后,才允许 Minecraft UI 线程继续执行 task.await() } - composeScene.density = if (uiScale <= 0) { - Density(1f, fontScale) - } else { - Density(uiScale / 2, fontScale) - } + // 因为修改 composeScene 状态会触发重绘 + // 必须先准备好渲染线程,才能设置 composeScene + composeScene.density = Density(uiScale, fontScale) composeScene.size = IntSize(width, height) composeScene.setContent(content) } @@ -63,15 +62,16 @@ class ComposeRender { val (width, height, uiScale, fontScale) = resizeData skiaRender.resize(width, height) composeCanvas = skiaRender.skiaSurface.canvas.asComposeCanvas() - composeScene.density = if (uiScale <= 0) { - Density(1f, fontScale) - } else { - Density(uiScale / 2, fontScale) - } + composeScene.density = Density(uiScale, fontScale) composeScene.size = IntSize(width, height) } + Snapshot.sendApplyNotifications() skiaRender.render { - composeScene.render(composeCanvas, System.nanoTime()) + try { + composeScene.render(composeCanvas, System.nanoTime()) + } catch (e: Exception) { + e.printStackTrace() + } } } @@ -88,13 +88,14 @@ class ComposeRender { fun destroy() { val task = composeScope.async { - skiaRender.destroy() + skiaRender.destroyRender() composeScene.close() } runBlocking { task.await() - composeScope.cancel() -// threadPool.close() } + composeScope.cancel() + threadPool.close() + skiaRender.destroyTexture() } } \ No newline at end of file diff --git a/src/client/kotlin/net/airgame/compose/ui/client/render/SkiaRender.kt b/src/client/kotlin/net/airgame/compose/ui/client/render/SkiaRender.kt index 2f79ca6..c30c6ab 100644 --- a/src/client/kotlin/net/airgame/compose/ui/client/render/SkiaRender.kt +++ b/src/client/kotlin/net/airgame/compose/ui/client/render/SkiaRender.kt @@ -33,7 +33,14 @@ class SkiaRender { lateinit var skiaContext: DirectContext; private set lateinit var skiaSurface: Surface; private set - fun init(width: Int, height: Int) { + /** + * 初始化离屏渲染 + * + * 由于需要创建新的窗口,并且在新窗口中与 Minecraft 窗口共享资源 + * + * 这个方法必须在 Minecraft UI 线程中调用 + */ + fun prepareTexture() { textureId = TextureUtil.generateTextureId() output = RenderLayer.of( "cpu_render_compose", @@ -43,14 +50,13 @@ class SkiaRender { 768 * 1024, RenderLayer.MultiPhaseParameters.builder() .texture(RenderPhase.TextureBase({ - RenderSystem.setShaderTexture(GL_TEXTURE0, textureId) + RenderSystem.setShaderTexture(0, textureId) }, {})) .program(RenderPhase.POSITION_TEXTURE_PROGRAM) .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) .depthTest(RenderPhase.LEQUAL_DEPTH_TEST) .build(false) ) - val oldWindow = glfwGetCurrentContext() // 在 OpenGL 里,一个上下文(Context)在同一时间只能被一个线程拥有。 // 在某些显卡驱动上,如果基于一个已经被占用的上下文去创建它的共享上下文,可能会导致 glfwCreateWindow 返回失败或死锁。 @@ -59,6 +65,13 @@ class SkiaRender { glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE) // 创建一个 1x1 的隐形窗口,并指定它和 oldWindow 共享仓库(纹理、VBO 全部通用)。 windowId = glfwCreateWindow(1, 1, "", NULL, oldWindow) + glfwMakeContextCurrent(oldWindow) + } + + /** + * 这个方法必须在渲染线程中调用,否则渲染线程可能因为没有初始化 OpenGL 而无法绘制任何内容 + */ + fun prepareRender(width: Int, height: Int) { glfwMakeContextCurrent(windowId) GL.createCapabilities() @@ -76,11 +89,6 @@ class SkiaRender { skiaSurface = Surface.makeFromBackendRenderTarget( skiaContext, renderTarget, SurfaceOrigin.TOP_LEFT, SurfaceColorFormat.RGBA_8888, ColorSpace.sRGB ) ?: throw IllegalStateException("Surface could not be created") - glfwMakeContextCurrent(oldWindow) - } - - fun switchWindow() { - glfwMakeContextCurrent(windowId) } fun render(renderTask: Runnable) { @@ -88,7 +96,8 @@ class SkiaRender { // 所以无需对 windowId 做保存与恢复 val oldWindow = glfwGetCurrentContext() glfwMakeContextCurrent(windowId) - skiaSurface.canvas.clear(Color.TRANSPARENT) + skiaSurface.canvas.clear(Color.WHITE) + skiaSurface.canvas.resetMatrix() renderTask.run() skiaContext.flush() glfwMakeContextCurrent(oldWindow) @@ -111,13 +120,16 @@ class SkiaRender { glfwMakeContextCurrent(oldWindow) } - fun destroy() { + fun destroyRender() { if (::skiaSurface.isInitialized) { skiaSurface.close() } if (::skiaContext.isInitialized) { skiaContext.close() } + } + + fun destroyTexture() { if (textureId != -1) { GlStateManager._deleteTexture(textureId) } 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 dd4a188..b3680c0 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 @@ -3,148 +3,45 @@ 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.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.unit.Density -import androidx.compose.ui.unit.IntSize -import com.mojang.blaze3d.platform.TextureUtil -import com.mojang.blaze3d.systems.RenderSystem -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.* +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.client.render.RenderLayer -import net.minecraft.client.render.RenderPhase -import net.minecraft.client.render.VertexFormat -import net.minecraft.client.render.VertexFormats import net.minecraft.text.Text -import org.jetbrains.skia.* -import org.jetbrains.skiko.FrameDispatcher -import org.lwjgl.glfw.GLFW.* -import org.lwjgl.opengl.ARBSync.* -import org.lwjgl.opengl.GL -import org.lwjgl.opengl.GL30.* -import org.lwjgl.opengl.GL32.GL_SYNC_FLUSH_COMMANDS_BIT -import org.lwjgl.opengl.GL32C.GL_SYNC_GPU_COMMANDS_COMPLETE -import org.lwjgl.system.MemoryUtil.NULL @OptIn(InternalComposeUiApi::class) class ComposeScreen( val composeContent: @Composable (ComposeScreen) -> 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 var frameBufferID = -1 - private val textureID = TextureUtil.generateTextureId() - private val composeLayer: RenderLayer by lazy { - RenderLayer.of( - "cpu_render_compose", - VertexFormats.POSITION_TEXTURE, - VertexFormat.DrawMode.QUADS, - 786432, - RenderLayer.MultiPhaseParameters.builder() - .texture(RenderPhase.TextureBase({ - RenderSystem.setShaderTexture(0, textureID) - }, {})) - .program(RenderPhase.POSITION_TEXTURE_PROGRAM) - .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) - .depthTest(RenderPhase.LEQUAL_DEPTH_TEST) - .build(false) - ) - } - - private var windowID: Long = -1 - private lateinit var directContext: DirectContext - private lateinit var surface: Surface - private lateinit var canvas: Canvas - - private val mainScope = MainScope() - - private val fenceSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) - private val needRedraw = atomic(true) - private val frameDispatcher = FrameDispatcher(mainScope) { - needRedraw.value = true - } - - private val mcGuiScale = MinecraftClient.getInstance().options.guiScale.value.toFloat() - private val fontScaleValue = 1f - private val scene: ComposeScene = CanvasLayersComposeScene( - density = if (mcGuiScale <= 0) { - Density(1f, fontScaleValue) - } else { - Density(mcGuiScale / 2, fontScaleValue) - }, - size = IntSize(windowWidth, windowHeight), - coroutineContext = Dispatchers.Main, - invalidate = { frameDispatcher.scheduleFrame() } - ).apply { - setContent { - composeContent(this@ComposeScreen) + 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() { - val oldWindow = glfwGetCurrentContext() - glfwMakeContextCurrent(0) - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE) - windowID = glfwCreateWindow(1, 1, "", NULL, oldWindow) - val initTask = mainScope.async { - glfwMakeContextCurrent(windowID) - GL.createCapabilities() - - frameBufferID = glGenFramebuffers() - glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID) - glBindTexture(GL_TEXTURE_2D, textureID) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, windowWidth, windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0) - - directContext = DirectContext.makeGL() - val renderTarget = BackendRenderTarget.makeGL( - windowWidth, windowHeight, 0, 8, frameBufferID, GL_RGBA8 - ) - surface = Surface.makeFromBackendRenderTarget( - directContext, renderTarget, SurfaceOrigin.TOP_LEFT, SurfaceColorFormat.RGBA_8888, ColorSpace.sRGB - ) ?: throw IllegalStateException("Surface could not be created") - canvas = surface.canvas.asComposeCanvas() - glfwMakeContextCurrent(0) + composeRender.init(windowWidth, windowHeight, guiScale, fontScale) { + composeContent(this) } - runBlocking { - initTask.await() - } - glfwMakeContextCurrent(oldWindow) + 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) - if (needRedraw.compareAndSet(true, false)) { - val oldWindow = glfwGetCurrentContext() - glfwMakeContextCurrent(0) - val renderTask = mainScope.async { - if (surface.isClosed) { - return@async - } - glfwMakeContextCurrent(windowID) - surface.canvas.clear(Color.TRANSPARENT) - scene.render(canvas, System.nanoTime()) - directContext.flush() - glfwMakeContextCurrent(0) - } - runBlocking { - renderTask.await() - } - glfwMakeContextCurrent(oldWindow) - glClientWaitSync(fenceSync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000) - } context.draw { - val vertexConsumer = it.getBuffer(composeLayer) + 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) @@ -154,51 +51,55 @@ class ComposeScreen( } 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.density = if (mcGuiScale <= 0) { - Density(1f, fontScaleValue) - } else { - Density(mcGuiScale / 2, fontScaleValue) - } - scene.size = IntSize(windowWidth, windowHeight) + 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) { - val x = MinecraftClient.getInstance().mouse.x.toFloat() - val y = MinecraftClient.getInstance().mouse.y.toFloat() - mainScope.launch { - scene.sendPointerEvent( + composeRender.post { + it.sendPointerEvent( PointerEventType.Move, - position = Offset(x, y) + position = getMousePosition() ) } } override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { - mainScope.launch { - val x = MinecraftClient.getInstance().mouse.x.toFloat() - val y = MinecraftClient.getInstance().mouse.y.toFloat() - scene.sendPointerEvent( + composeRender.post { + it.sendPointerEvent( PointerEventType.Press, - button = PointerButton.Primary, - position = Offset(x, y) + button = getMouseButton(button), + position = getMousePosition() ) } return true } override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { - mainScope.launch { - val x = MinecraftClient.getInstance().mouse.x.toFloat() - val y = MinecraftClient.getInstance().mouse.y.toFloat() - scene.sendPointerEvent( + composeRender.post { + it.sendPointerEvent( PointerEventType.Release, - button = PointerButton.Primary, - position = Offset(x, y) + button = getMouseButton(button), + position = getMousePosition() ) } return true @@ -207,13 +108,11 @@ class ComposeScreen( override fun mouseScrolled( mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double ): Boolean { - val x = MinecraftClient.getInstance().mouse.x.toFloat() - val y = MinecraftClient.getInstance().mouse.y.toFloat() - mainScope.launch { - scene.sendPointerEvent( + composeRender.post { + it.sendPointerEvent( PointerEventType.Scroll, button = PointerButton.Tertiary, - position = Offset(x, y), + position = getMousePosition(), scrollDelta = Offset(horizontalAmount.toFloat(), -verticalAmount.toFloat()) ) } @@ -222,17 +121,6 @@ class ComposeScreen( override fun close() { MinecraftClient.getInstance().setScreen(null) - if (textureID != 0) { - glDeleteTextures(textureID) - } - if (frameBufferID != -1) { - glDeleteFramebuffers(frameBufferID) - frameBufferID = -1 - } - glDeleteSync(fenceSync) - directContext.close() - surface.close() - scene.close() - mainScope.cancel() + composeRender.destroy() } } \ No newline at end of file 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 deleted file mode 100644 index 625290a..0000000 --- a/src/client/kotlin/net/airgame/compose/ui/client/screen/ComposeScreenV2.kt +++ /dev/null @@ -1,126 +0,0 @@ -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