feat: 初版提交
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.gradle
|
||||||
|
.idea
|
||||||
|
.kotlin
|
||||||
|
build
|
||||||
|
run
|
80
build.gradle.kts
Normal file
80
build.gradle.kts
Normal file
@@ -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<MavenPublication>("mavenJava") {
|
||||||
|
artifactId = project.property("archives_base_name") as String
|
||||||
|
from(components["java"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
}
|
||||||
|
}
|
18
gradle.properties
Normal file
18
gradle.properties
Normal file
@@ -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
|
1
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
1
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
17
settings.gradle.kts
Normal file
17
settings.gradle.kts
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
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
|
||||||
|
}
|
||||||
|
}
|
9
src/main/kotlin/net/airgame/compose/ui/ComposeUI.kt
Normal file
9
src/main/kotlin/net/airgame/compose/ui/ComposeUI.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package net.airgame.compose.ui
|
||||||
|
|
||||||
|
import net.fabricmc.api.ModInitializer
|
||||||
|
|
||||||
|
class ComposeUI : ModInitializer {
|
||||||
|
|
||||||
|
override fun onInitialize() {
|
||||||
|
}
|
||||||
|
}
|
11
src/main/resources/compose-ui-mod.mixins.json
Normal file
11
src/main/resources/compose-ui-mod.mixins.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "net.airgame.compose.ui.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_21",
|
||||||
|
"mixins": [
|
||||||
|
],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
33
src/main/resources/fabric.mod.json
Normal file
33
src/main/resources/fabric.mod.json
Normal file
@@ -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}"
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user