diff --git a/build.gradle.kts b/build.gradle.kts index 136f29d..ddd9119 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,11 +2,9 @@ plugins { id("fabric-loom") version "1.2-SNAPSHOT" id("org.jetbrains.kotlin.jvm") version "1.8.22" id("maven-publish") + id("com.github.johnrengelman.shadow") version "8.1.1" } -group = property("maven_group")!! -version = property("mod_version")!! - dependencies { minecraft("com.mojang:minecraft:${property("minecraft_version")}") mappings("net.fabricmc:yarn:${property("yarn_mappings")}:v2") @@ -14,6 +12,14 @@ dependencies { modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}") modImplementation("net.fabricmc:fabric-language-kotlin:${property("fabric_kotlin_version")}") + + val imguiVersion = property("imgui_version")!! + + implementation(shadow("io.github.spair:imgui-java-binding:$imguiVersion")!!) + implementation(shadow("io.github.spair:imgui-java-lwjgl3:$imguiVersion")!!) + implementation(shadow("io.github.spair:imgui-java-natives-windows:$imguiVersion")!!) + implementation(shadow("io.github.spair:imgui-java-natives-linux:$imguiVersion")!!) + implementation(shadow("io.github.spair:imgui-java-natives-macos:$imguiVersion")!!) } loom { @@ -36,6 +42,20 @@ tasks { } } + shadowJar { + configurations = listOf(project.configurations.shadow.get()) + dependencies { + exclude(dependency("org.lwjgl:lwjgl")) + exclude(dependency("org.lwjgl:lwjgl-glfw")) + exclude(dependency("org.lwjgl:lwjgl-opengl")) + } + } + + remapJar { + dependsOn(shadowJar) + mustRunAfter(shadowJar) + } + compileKotlin { kotlinOptions.jvmTarget = "17" } diff --git a/gradle.properties b/gradle.properties index 13bd93b..cc0e199 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,8 +6,9 @@ yarn_mappings=1.20+build.1 loader_version=0.14.21 fabric_kotlin_version=1.9.5+kotlin.1.8.22 -mod_version=1.0.6 -maven_group=pm.n2 +version=1.1.0 +group=pm.n2 archives_base_name=hajlib fabric_version=0.83.0+1.20 +imgui_version=1.86.10 diff --git a/src/main/java/pm/n2/hajlib/mixin/RenderSystemMixin.java b/src/main/java/pm/n2/hajlib/mixin/RenderSystemMixin.java new file mode 100644 index 0000000..ae5d0fa --- /dev/null +++ b/src/main/java/pm/n2/hajlib/mixin/RenderSystemMixin.java @@ -0,0 +1,19 @@ +package pm.n2.hajlib.mixin; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import pm.n2.hajlib.imgui.ImGuiManager; + +@Mixin(RenderSystem.class) +public class RenderSystemMixin { + @Inject(at = @At("HEAD"), method = "flipFrame") + private static void flipFrame(long window, CallbackInfo ci) { + MinecraftClient.getInstance().getProfiler().push("ImGui"); + ImGuiManager.INSTANCE.onFrameRender$hajlib(); + MinecraftClient.getInstance().getProfiler().pop(); + } +} diff --git a/src/main/java/pm/n2/hajlib/mixin/WindowMixin.java b/src/main/java/pm/n2/hajlib/mixin/WindowMixin.java new file mode 100644 index 0000000..f1b193c --- /dev/null +++ b/src/main/java/pm/n2/hajlib/mixin/WindowMixin.java @@ -0,0 +1,25 @@ +package pm.n2.hajlib.mixin; + +import net.minecraft.client.WindowEventHandler; +import net.minecraft.client.WindowSettings; +import net.minecraft.client.util.MonitorTracker; +import net.minecraft.client.util.Window; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import pm.n2.hajlib.imgui.ImGuiManager; + +@Mixin(Window.class) +public class WindowMixin { + @Final + @Shadow + private long handle; + + @Inject(at = @At("TAIL"), method = "") + private void init(WindowEventHandler eventHandler, MonitorTracker monitorTracker, WindowSettings settings, String videoMode, String title, CallbackInfo ci) { + ImGuiManager.INSTANCE.onGLFWInit$hajlib(handle); + } +} diff --git a/src/main/kotlin/pm/n2/hajlib/event/EventManager.kt b/src/main/kotlin/pm/n2/hajlib/event/EventManager.kt index cc492cf..67214de 100644 --- a/src/main/kotlin/pm/n2/hajlib/event/EventManager.kt +++ b/src/main/kotlin/pm/n2/hajlib/event/EventManager.kt @@ -8,7 +8,7 @@ import kotlin.reflect.jvm.javaMethod import kotlin.reflect.jvm.javaType typealias UnregisterFunc = () -> Unit -typealias EventHandlerFunc = (obj: T, unregister: UnregisterFunc) -> Any +typealias EventHandlerFunc = (obj: T, unregister: UnregisterFunc) -> Any? typealias EventTarget = Pair /** diff --git a/src/main/kotlin/pm/n2/hajlib/imgui/ImGuiEvent.kt b/src/main/kotlin/pm/n2/hajlib/imgui/ImGuiEvent.kt new file mode 100644 index 0000000..9ee2abc --- /dev/null +++ b/src/main/kotlin/pm/n2/hajlib/imgui/ImGuiEvent.kt @@ -0,0 +1,5 @@ +package pm.n2.hajlib.imgui + +sealed class ImGuiEvent { + object Draw : ImGuiEvent() +} diff --git a/src/main/kotlin/pm/n2/hajlib/imgui/ImGuiManager.kt b/src/main/kotlin/pm/n2/hajlib/imgui/ImGuiManager.kt new file mode 100644 index 0000000..9c1f71a --- /dev/null +++ b/src/main/kotlin/pm/n2/hajlib/imgui/ImGuiManager.kt @@ -0,0 +1,100 @@ +package pm.n2.hajlib.imgui + +import imgui.ImFontConfig +import imgui.ImGui +import imgui.flag.ImGuiConfigFlags +import imgui.flag.ImGuiKey +import imgui.gl3.ImGuiImplGl3 +import imgui.glfw.ImGuiImplGlfw +import net.fabricmc.loader.api.FabricLoader +import org.lwjgl.glfw.GLFW +import pm.n2.hajlib.event.EventManager +import kotlin.properties.Delegates + +/** + * A wrapper around ImGui, ported from [imgui-quilt](https://git.gaycatgirl.sex/evie/imgui-quilt). + * Subscribe to the Tick event via the [EventManager] to draw your UI. + * @see ImGuiScreen + */ +object ImGuiManager { + val eventManager = EventManager() + + private val imguiGLFW = ImGuiImplGlfw() + private val imguiGL3 = ImGuiImplGl3() + private var windowHandle by Delegates.notNull() + + internal fun onGLFWInit(handle: Long) { + ImGui.createContext() + val io = ImGui.getIO() + + io.iniFilename = FabricLoader.getInstance() + .configDir + .resolve("imgui.ini") + .toString() + io.addConfigFlags( + ImGuiConfigFlags.NavEnableKeyboard + or ImGuiConfigFlags.DockingEnable + or ImGuiConfigFlags.ViewportsEnable + ) + + val fontAtlas = io.fonts + val fontConfig = ImFontConfig() + fontAtlas.addFontDefault() + + fontConfig.mergeMode = true + fontConfig.pixelSnapH = true + fontConfig.destroy() + + // APPLE MOMENT + val isTimCooksBitch = System.getProperty("os.name").lowercase().contains("mac") + imguiGL3.init(if (isTimCooksBitch) "#version 140" else "#version 130") + imguiGLFW.init(handle, true) + + windowHandle = handle + } + + private fun fixInputs() { + val io = ImGui.getIO() + + // Something is trying to overwrite this? + io.setKeyMap(ImGuiKey.Tab, GLFW.GLFW_KEY_TAB) + io.setKeyMap(ImGuiKey.LeftArrow, GLFW.GLFW_KEY_LEFT) + io.setKeyMap(ImGuiKey.RightArrow, GLFW.GLFW_KEY_RIGHT) + io.setKeyMap(ImGuiKey.UpArrow, GLFW.GLFW_KEY_UP) + io.setKeyMap(ImGuiKey.DownArrow, GLFW.GLFW_KEY_DOWN) + io.setKeyMap(ImGuiKey.PageUp, GLFW.GLFW_KEY_PAGE_UP) + io.setKeyMap(ImGuiKey.PageDown, GLFW.GLFW_KEY_PAGE_DOWN) + io.setKeyMap(ImGuiKey.Home, GLFW.GLFW_KEY_HOME) + io.setKeyMap(ImGuiKey.End, GLFW.GLFW_KEY_END) + io.setKeyMap(ImGuiKey.Insert, GLFW.GLFW_KEY_INSERT) + io.setKeyMap(ImGuiKey.Delete, GLFW.GLFW_KEY_DELETE) + io.setKeyMap(ImGuiKey.Backspace, GLFW.GLFW_KEY_BACKSPACE) + io.setKeyMap(ImGuiKey.Space, GLFW.GLFW_KEY_SPACE) + io.setKeyMap(ImGuiKey.Enter, GLFW.GLFW_KEY_ENTER) + io.setKeyMap(ImGuiKey.Escape, GLFW.GLFW_KEY_ESCAPE) + io.setKeyMap(ImGuiKey.KeyPadEnter, GLFW.GLFW_KEY_KP_ENTER) + io.setKeyMap(ImGuiKey.A, GLFW.GLFW_KEY_A) + io.setKeyMap(ImGuiKey.C, GLFW.GLFW_KEY_C) + io.setKeyMap(ImGuiKey.V, GLFW.GLFW_KEY_V) + io.setKeyMap(ImGuiKey.X, GLFW.GLFW_KEY_X) + io.setKeyMap(ImGuiKey.Y, GLFW.GLFW_KEY_Y) + io.setKeyMap(ImGuiKey.Z, GLFW.GLFW_KEY_Z) + } + + internal fun onFrameRender() { + imguiGLFW.newFrame() + ImGui.newFrame() + + eventManager.dispatch(ImGuiEvent.Draw) + fixInputs() + + ImGui.render() + + imguiGL3.renderDrawData(ImGui.getDrawData()) + + val backupWindowPtr = GLFW.glfwGetCurrentContext() + ImGui.updatePlatformWindows() + ImGui.renderPlatformWindowsDefault() + GLFW.glfwMakeContextCurrent(backupWindowPtr) + } +} diff --git a/src/main/kotlin/pm/n2/hajlib/imgui/ImGuiScreen.kt b/src/main/kotlin/pm/n2/hajlib/imgui/ImGuiScreen.kt new file mode 100644 index 0000000..d509e64 --- /dev/null +++ b/src/main/kotlin/pm/n2/hajlib/imgui/ImGuiScreen.kt @@ -0,0 +1,49 @@ +package pm.n2.hajlib.imgui + +import imgui.ImGui +import net.minecraft.client.gui.screen.Screen +import net.minecraft.text.Text +import pm.n2.hajlib.event.UnregisterFunc + +/** + * A screen that draws ImGui. + * Extend this class and insert your ImGui code into the [drawImGui] function, + * and it will be drawn on the screen. + */ +open class ImGuiScreen(title: Text) : Screen(title) { + var initialized: Boolean = false // init() gets called on resize, too + + private val drawLambda = { _: ImGuiEvent.Draw, _: UnregisterFunc -> + this.drawImGui() + } + + open fun drawImGui() {} + + override fun init() { + if (!initialized) { + ImGuiManager.eventManager.registerFunc(ImGuiEvent.Draw::class, drawLambda) + + val io = ImGui.getIO() + io.wantCaptureKeyboard = true + io.wantCaptureMouse = true + io.wantTextInput = true + + initialized = true + } + } + + override fun removed() { + if (initialized) { + ImGuiManager.eventManager.unregisterFunc(ImGuiEvent.Draw::class, drawLambda) + + val io = ImGui.getIO() + io.wantCaptureKeyboard = false + io.wantCaptureMouse = false + io.wantTextInput = false + + initialized = false + } + + super.removed() + } +} diff --git a/src/main/resources/hajlib.mixins.json b/src/main/resources/hajlib.mixins.json index 56a030e..b95ce09 100644 --- a/src/main/resources/hajlib.mixins.json +++ b/src/main/resources/hajlib.mixins.json @@ -7,5 +7,9 @@ ], "injectors": { "defaultRequire": 1 - } + }, + "client": [ + "RenderSystemMixin", + "WindowMixin" + ] }