package net.shadowfacts.cacao import net.minecraft.client.MinecraftClient import net.minecraft.client.gui.screen.Screen import net.minecraft.client.util.math.MatrixStack import net.minecraft.sound.SoundEvents import net.minecraft.text.LiteralText import net.minecraft.text.Text import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.util.KeyModifiers import net.shadowfacts.cacao.util.MouseButton import net.shadowfacts.cacao.util.RenderHelper import net.shadowfacts.cacao.window.Window import org.lwjgl.glfw.GLFW import java.util.* /** * This class serves as the bridge between Cacao and a Minecraft [Screen]. It renders Cacao [Window]s in Minecraft and * sends input events from Minecraft back to Cacao objects. * * @author shadowfacts */ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title), AbstractCacaoScreen { // _windows is the internal, mutable object, since we only want it to by mutated by the add/removeWindow methods. private val _windows = LinkedList() /** * The list of windows that belong to this screen. * * The window at the end of this list is the active window is the only window that will receive input events. * * This list should never be modified directly, only by using the [addWindow]/[removeWindow] methods. */ override val windows: List = _windows private var hasAppeared = false /** * Adds the given window to this screen's window list at the given position. * * @param window The Window to add to this screen. * @param index The index to insert the window into the window list at. * @return The window that was added, as a convenience. */ override fun addWindow(window: T, index: Int): T { if (hasAppeared) { window.viewController.viewWillAppear() } _windows.add(index, window) window.screen = this window.wasAdded() window.resize(width, height) return window } /** * Adds the given window to the end of this screen's window list, making it the active window. */ override fun addWindow(window: T): T { return addWindow(window, _windows.size) } /** * Removes the given window from this screen's window list. */ override fun removeWindow(window: Window) { _windows.remove(window) if (windows.isEmpty()) { close() } } override fun screenWillAppear() { windows.forEach { it.viewController.viewWillAppear() } } override fun init() { super.init() windows.forEach { it.resize(width, height) } } override fun close() { super.close() windows.forEach { it.viewController.viewWillDisappear() it.viewController.viewDidDisappear() // resign the current first responder (if any) it.firstResponder = null } } override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { if (client != null) { // workaround this.minecraft sometimes being null causing a crash renderBackground(matrixStack) } val mouse = Point(mouseX, mouseY) windows.forEach { it.draw(matrixStack, mouse, delta) } } override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { val window = windows.lastOrNull() val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button)) return if (result == true) { RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK) true } else { false } } override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { val window = windows.lastOrNull() val startPoint = Point(mouseX, mouseY) val delta = Point(deltaX, deltaY) val result = window?.mouseDragged(startPoint, delta, MouseButton.fromMC(button)) return result == true } override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { val window = windows.lastOrNull() val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button)) return result == true } override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean { val window = windows.lastOrNull() val result = window?.mouseScrolled(Point(mouseX, mouseY), amount) return result == true } override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { if (keyCode == GLFW.GLFW_KEY_ESCAPE) { windows.lastOrNull()?.removeFromScreen() return true } else { val modifiersSet by lazy { KeyModifiers(modifiers) } if (findResponder { it.keyPressed(keyCode, modifiersSet) }) { return true } return super.keyPressed(keyCode, scanCode, modifiers) } } override fun keyReleased(i: Int, j: Int, k: Int): Boolean { return super.keyReleased(i, j, k) } override fun charTyped(char: Char, modifiers: Int): Boolean { val modifiersSet by lazy { KeyModifiers(modifiers) } if (findResponder { it.charTyped(char, modifiersSet) }) { return true } return super.charTyped(char, modifiers) } override fun shouldCloseOnEsc(): Boolean { return false } } fun AbstractCacaoScreen.findResponder(fn: (Responder) -> Boolean): Boolean { var responder = windows.lastOrNull()?.firstResponder while (responder != null) { if (fn(responder)) { return true } responder = responder.nextResponder } return false }