package moe.nea.firmament.features.inventory.storageoverlay import io.github.notenoughupdates.moulconfig.gui.GuiContext import io.github.notenoughupdates.moulconfig.gui.MouseEvent import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent import io.github.notenoughupdates.moulconfig.gui.component.TextComponent import me.shedaniel.math.Point import me.shedaniel.math.Rectangle import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.screen.slot.Slot import net.minecraft.text.Text import net.minecraft.util.Identifier import moe.nea.firmament.gui.EmptyComponent import moe.nea.firmament.gui.FirmButtonComponent import moe.nea.firmament.util.MC import moe.nea.firmament.util.MoulConfigUtils.adopt import moe.nea.firmament.util.MoulConfigUtils.clickMCComponentInPlace import moe.nea.firmament.util.MoulConfigUtils.drawMCComponentInPlace import moe.nea.firmament.util.assertTrueOr import moe.nea.firmament.util.customgui.customGui class StorageOverlayScreen : Screen(Text.literal("")) { companion object { val PLAYER_WIDTH = 184 val PLAYER_HEIGHT = 91 val PLAYER_Y_INSET = 3 val SLOT_SIZE = 18 val PADDING = 10 val PAGE_WIDTH = SLOT_SIZE * 9 val HOTBAR_X = 12 val HOTBAR_Y = 67 val MAIN_INVENTORY_Y = 9 val SCROLL_BAR_WIDTH = 8 val SCROLL_BAR_HEIGHT = 16 val CONTROL_WIDTH = 70 val CONTROL_BACKGROUND_WIDTH = CONTROL_WIDTH + PLAYER_Y_INSET val CONTROL_HEIGHT = 100 } var isExiting: Boolean = false var scroll: Float = 0F var pageWidthCount = StorageOverlay.TConfig.columns inner class Measurements { val innerScrollPanelWidth = PAGE_WIDTH * pageWidthCount + (pageWidthCount - 1) * PADDING val overviewWidth = innerScrollPanelWidth + 3 * PADDING + SCROLL_BAR_WIDTH val x = width / 2 - overviewWidth / 2 val overviewHeight = minOf(3 * 18 * 6, height - PLAYER_HEIGHT - minOf(80, height / 10)) val innerScrollPanelHeight = overviewHeight - PADDING * 2 val y = height / 2 - (overviewHeight + PLAYER_HEIGHT) / 2 val playerX = width / 2 - PLAYER_WIDTH / 2 val playerY = y + overviewHeight - PLAYER_Y_INSET val controlX = x - CONTROL_WIDTH val controlY = y + overviewHeight / 2 - CONTROL_HEIGHT / 2 val totalWidth = overviewWidth val totalHeight = overviewHeight - PLAYER_Y_INSET + PLAYER_HEIGHT } var measurements = Measurements() var lastRenderedInnerHeight = 0 public override fun init() { super.init() pageWidthCount = StorageOverlay.TConfig.columns .coerceAtMost((width - PADDING) / (PAGE_WIDTH + PADDING)) .coerceAtLeast(1) measurements = Measurements() scroll = scroll.coerceAtMost(getMaxScroll()).coerceAtLeast(0F) } override fun mouseScrolled( mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double ): Boolean { scroll = (scroll + StorageOverlay.adjustScrollSpeed(verticalAmount)).toFloat() .coerceAtMost(getMaxScroll()) .coerceAtLeast(0F) return true } fun getMaxScroll() = lastRenderedInnerHeight.toFloat() - getScrollPanelInner().height val playerInventorySprite = Identifier.of("firmament:storageoverlay/player_inventory") val upperBackgroundSprite = Identifier.of("firmament:storageoverlay/upper_background") val slotRowSprite = Identifier.of("firmament:storageoverlay/storage_row") val scrollbarBackground = Identifier.of("firmament:storageoverlay/scroll_bar_background") val scrollbarKnob = Identifier.of("firmament:storageoverlay/scroll_bar_knob") val controllerBackground = Identifier.of("firmament:storageoverlay/storage_controls") override fun close() { isExiting = true super.close() } override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { super.render(context, mouseX, mouseY, delta) drawBackgrounds(context) drawPages(context, mouseX, mouseY, delta, null, null, Point()) drawScrollBar(context) drawPlayerInventory(context, mouseX, mouseY, delta) drawControls(context, mouseX, mouseY) } fun getScrollbarPercentage(): Float { return scroll / getMaxScroll() } fun drawScrollBar(context: DrawContext) { val sbRect = getScrollBarRect() context.drawGuiTexture( scrollbarBackground, sbRect.minX, sbRect.minY, sbRect.width, sbRect.height, ) context.drawGuiTexture( scrollbarKnob, sbRect.minX, sbRect.minY + (getScrollbarPercentage() * (sbRect.height - SCROLL_BAR_HEIGHT)).toInt(), SCROLL_BAR_WIDTH, SCROLL_BAR_HEIGHT ) } fun editPages() { isExiting = true val hs = MC.screen as? HandledScreen<*> if (StorageBackingHandle.fromScreen(hs) is StorageBackingHandle.Overview) { hs.customGui = null } else { MC.sendCommand("storage") } } val guiContext = GuiContext(EmptyComponent()) private val knobStub = EmptyComponent() val editButton = PanelComponent(ColumnComponent(FirmButtonComponent(TextComponent("Edit"), action = ::editPages)), 8, PanelComponent.DefaultBackgroundRenderer.TRANSPARENT) init { guiContext.adopt(editButton) guiContext.adopt(knobStub) } fun drawControls(context: DrawContext, mouseX: Int, mouseY: Int) { context.drawGuiTexture( controllerBackground, measurements.controlX, measurements.controlY, CONTROL_BACKGROUND_WIDTH, CONTROL_HEIGHT) context.drawMCComponentInPlace( editButton, measurements.controlX, measurements.controlY, CONTROL_WIDTH, CONTROL_HEIGHT, mouseX, mouseY) } fun drawBackgrounds(context: DrawContext) { context.drawGuiTexture(upperBackgroundSprite, measurements.x, measurements.y, 0, measurements.overviewWidth, measurements.overviewHeight) context.drawGuiTexture(playerInventorySprite, measurements.playerX, measurements.playerY, 0, PLAYER_WIDTH, PLAYER_HEIGHT) } fun getPlayerInventorySlotPosition(int: Int): Pair { if (int < 9) { return Pair(measurements.playerX + int * SLOT_SIZE + HOTBAR_X, HOTBAR_Y + measurements.playerY) } return Pair( measurements.playerX + (int % 9) * SLOT_SIZE + HOTBAR_X, measurements.playerY + (int / 9 - 1) * SLOT_SIZE + MAIN_INVENTORY_Y ) } fun drawPlayerInventory(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { val items = MC.player?.inventory?.main ?: return items.withIndex().forEach { (index, item) -> val (x, y) = getPlayerInventorySlotPosition(index) context.drawItem(item, x, y, 0) context.drawItemInSlot(textRenderer, item, x, y) } } fun getScrollBarRect(): Rectangle { return Rectangle(measurements.x + PADDING + measurements.innerScrollPanelWidth + PADDING, measurements.y + PADDING, SCROLL_BAR_WIDTH, measurements.innerScrollPanelHeight) } fun getScrollPanelInner(): Rectangle { return Rectangle(measurements.x + PADDING, measurements.y + PADDING, measurements.innerScrollPanelWidth, measurements.innerScrollPanelHeight) } fun createScissors(context: DrawContext) { val rect = getScrollPanelInner() context.enableScissor( rect.minX, rect.minY, rect.maxX, rect.maxY ) } fun drawPages( context: DrawContext, mouseX: Int, mouseY: Int, delta: Float, excluding: StoragePageSlot?, slots: List?, slotOffset: Point ) { createScissors(context) val data = StorageOverlay.Data.data ?: StorageData() layoutedForEach(data) { rect, page, inventory -> drawPage(context, rect.x, rect.y, page, inventory, if (excluding == page) slots else null, slotOffset ) } context.disableScissor() } var knobGrabbed: Boolean get() = guiContext.focusedElement == knobStub set(value) = knobStub.setFocus(value) override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { return mouseClicked(mouseX, mouseY, button, null) } override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { if (knobGrabbed) { knobGrabbed = false return true } if (clickMCComponentInPlace(editButton, measurements.controlX, measurements.controlY, CONTROL_WIDTH, CONTROL_HEIGHT, mouseX.toInt(), mouseY.toInt(), MouseEvent.Click(button, false)) ) return true return super.mouseReleased(mouseX, mouseY, button) } override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { if (knobGrabbed) { val sbRect = getScrollBarRect() val percentage = (mouseY - sbRect.getY()) / sbRect.getHeight() scroll = (getMaxScroll() * percentage).toFloat() mouseScrolled(0.0, 0.0, 0.0, 0.0) return true } return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY) } fun mouseClicked(mouseX: Double, mouseY: Double, button: Int, activePage: StoragePageSlot?): Boolean { if (getScrollPanelInner().contains(mouseX, mouseY)) { val data = StorageOverlay.Data.data ?: StorageData() layoutedForEach(data) { rect, page, _ -> if (rect.contains(mouseX, mouseY) && activePage != page && button == 0) { page.navigateTo() return true } } return false } val sbRect = getScrollBarRect() if (sbRect.contains(mouseX, mouseY)) { val percentage = (mouseY - sbRect.getY()) / sbRect.getHeight() scroll = (getMaxScroll() * percentage).toFloat() mouseScrolled(0.0, 0.0, 0.0, 0.0) knobGrabbed = true return true } if (clickMCComponentInPlace(editButton, measurements.controlX, measurements.controlY, CONTROL_WIDTH, CONTROL_HEIGHT, mouseX.toInt(), mouseY.toInt(), MouseEvent.Click(button, true)) ) return true return false } private inline fun layoutedForEach( data: StorageData, func: ( rectangle: Rectangle, page: StoragePageSlot, inventory: StorageData.StorageInventory, ) -> Unit ) { var yOffset = -scroll.toInt() var xOffset = 0 var maxHeight = 0 for ((page, inventory) in data.storageInventories.entries) { val currentHeight = inventory.inventory?.let { it.rows * SLOT_SIZE + 4 + textRenderer.fontHeight } ?: 18 maxHeight = maxOf(maxHeight, currentHeight) val rect = Rectangle( measurements.x + PADDING + (PAGE_WIDTH + PADDING) * xOffset, yOffset + measurements.y + PADDING, PAGE_WIDTH, currentHeight ) func(rect, page, inventory) xOffset++ if (xOffset >= pageWidthCount) { yOffset += maxHeight xOffset = 0 maxHeight = 0 } } lastRenderedInnerHeight = maxHeight + yOffset + scroll.toInt() } fun drawPage( context: DrawContext, x: Int, y: Int, page: StoragePageSlot, inventory: StorageData.StorageInventory, slots: List?, slotOffset: Point, ): Int { val inv = inventory.inventory if (inv == null) { context.drawGuiTexture(upperBackgroundSprite, x, y, PAGE_WIDTH, 18) context.drawText(textRenderer, Text.literal("TODO: open this page"), x + 4, y + 4, -1, true) return 18 } assertTrueOr(slots == null || slots.size == inv.stacks.size) { return 0 } val name = page.defaultName() context.drawText(textRenderer, Text.literal(name), x + 4, y + 2, if (slots == null) 0xFFFFFFFF.toInt() else 0xFFFFFF00.toInt(), true) context.drawGuiTexture(slotRowSprite, x, y + 4 + textRenderer.fontHeight, PAGE_WIDTH, inv.rows * SLOT_SIZE) inv.stacks.forEachIndexed { index, stack -> val slotX = (index % 9) * SLOT_SIZE + x + 1 val slotY = (index / 9) * SLOT_SIZE + y + 4 + textRenderer.fontHeight + 1 if (slots == null) { context.drawItem(stack, slotX, slotY) context.drawItemInSlot(textRenderer, stack, slotX, slotY) } else { val slot = slots[index] slot.x = slotX - slotOffset.x slot.y = slotY - slotOffset.y } } return inv.rows * SLOT_SIZE + 4 + textRenderer.fontHeight } fun getBounds(): List { return listOf( Rectangle(measurements.x, measurements.y, measurements.overviewWidth, measurements.overviewHeight), Rectangle(measurements.playerX, measurements.playerY, PLAYER_WIDTH, PLAYER_HEIGHT), Rectangle(measurements.controlX, measurements.controlY, CONTROL_WIDTH, CONTROL_HEIGHT)) } }