feat: 拆分 compose 和 sika 渲染代码
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven("https://maven.aliyun.com/repository/public")
|
||||
mavenLocal()
|
||||
maven("https://maven.fabricmc.net/") {
|
||||
name = "Fabric"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user