PhysicalConnectivity/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt

186 lines
5.1 KiB
Kotlin

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<Window>()
/**
* 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<Window> = _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 <T: Window> 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 <T : Window> 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()) {
onClose()
}
}
override fun screenWillAppear() {
windows.forEach {
it.viewController.viewWillAppear()
}
}
override fun init() {
super.init()
windows.forEach {
it.resize(width, height)
}
}
override fun onClose() {
super.onClose()
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
}