From 75fe096059907cb9069907005f9c55e6a2194a8e Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Thu, 1 Sep 2022 00:20:39 +0700 Subject: [PATCH] Improved, but not finished, android station GUI --- .../otm/client/screen/AndroidStationScreen.kt | 108 ++++++-- .../client/screen/ExoSuitInventoryScreen.kt | 18 +- .../otm/client/screen/panels/EditablePanel.kt | 235 +++++++++++++----- .../screen/panels/EntityRendererPanel.kt | 58 +++++ .../mc/otm/menu/AndroidStationMenu.kt | 4 + .../mc/otm/menu/ExoSuitInventoryMenu.kt | 7 +- .../ru/dbotthepony/mc/otm/menu/MatteryMenu.kt | 7 + .../mc/otm/menu/widget/LevelGaugeWidget.kt | 2 + 8 files changed, 336 insertions(+), 103 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt index 9405bcca2..4cf98dbbc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt @@ -31,6 +31,7 @@ import ru.dbotthepony.mc.otm.network.AndroidResearchRequestPacket import ru.dbotthepony.mc.otm.registry.MRegistry import java.util.* import kotlin.collections.ArrayList +import kotlin.properties.Delegates private fun exploreTree(research: AndroidResearchType<*>, seen: MutableSet>, result: MutableList>) { if (!seen.add(research)) { @@ -246,7 +247,7 @@ private class Tree(val node: AndroidResearchType<*>) : Iterable { } } -private open class AndroidResearchButton( +private class AndroidResearchButton( parent: EditablePanel, private val node: AndroidResearch, private val lines: List, @@ -410,22 +411,38 @@ private open class AndroidResearchButton( class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: Inventory, p_97743_: Component) : MatteryScreen(p_97741_, p_97742_, p_97743_) { - private var canvas: DraggableCanvasPanel? = null - private var research: FramePanel? = null - var hoveredResearch: AndroidResearch? = null - - private fun openResearchTree() { - val window = minecraft!!.window - - research = FramePanel(this, null, 0f, 0f, window.guiScaledWidth * 0.8f, window.guiScaledHeight * 0.8f, TranslatableComponent("otm.gui.android_research")) - + private fun makeCanvas(isPreview: Boolean): DraggableCanvasPanel { val rows = Int2ObjectAVLTreeMap() - canvas = object : DraggableCanvasPanel(this@AndroidStationScreen, research, width = (GRID_WIDTH * 22).toFloat(), height = 0f) { + val canvas = object : DraggableCanvasPanel(this@AndroidStationScreen, null) { + private val random = Random() + override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { drawColor = RGBAColor.BLACK drawRect(stack, 0f, 0f, width, height) } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (isPreview) { + openResearchTree() + return true + } + + return super.mouseClickedInner(x, y, button) + } + + override fun tick() { + super.tick() + + if (isPreview) { + xOffset -= 1f + + if (xOffset < -boundingWidth) { + xOffset = width + yOffset = random.nextFloat(-boundingHeight + 18f, 0f) + } + } + } } minecraft?.player?.getCapability(MatteryCapability.MATTERY_PLAYER)?.ifPresentK { @@ -475,7 +492,26 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I } } - canvas!!.dock = Dock.FILL + for (row in rows.values) { + row.sizeToContents() + row.acceptMouseInput = !isPreview + } + + return canvas + } + + private var research: FramePanel? = null + private var playerStrip: EditablePanel by Delegates.notNull() + var hoveredResearch: AndroidResearch? = null + + private fun openResearchTree() { + val window = minecraft!!.window + + research = FramePanel(this, null, 0f, 0f, window.guiScaledWidth * 0.8f, window.guiScaledHeight * 0.8f, TranslatableComponent("otm.gui.android_research")) + + val researchCanvas = makeCanvas(false) + researchCanvas.dock = Dock.FILL + researchCanvas.parent = research val bottom = EditablePanel(this, research, 0f, 0f, 0f, 20f) val close = ButtonPanel(this, bottom, 0f, 0f, 90f, 20f, TranslatableComponent("otm.container.matter_panel.close")) @@ -486,7 +522,7 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I close.bind { research!!.remove() } bottom.setDockMargin(0f, 0f, 4f, 4f) - canvas!!.setDockMargin(4f, 4f, 4f, 4f) + researchCanvas.setDockMargin(4f, 4f, 4f, 4f) research!!.toScreenCenter() addPanel(research!!) @@ -505,14 +541,48 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I } override fun makeMainFrame(): FramePanel { - val frame = super.makeMainFrame()!! + val frame = FramePanel(this, 200f, 100f, title) - WidePowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - SlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - SlotPanel(this, frame, menu.androidBattery, 38f, 17f) + val stripLeft = EditablePanel(this, frame, width = AbstractSlotPanel.SIZE) - val button = ButtonPanel(this, frame, 38f, 69f, 124f, 20f, TranslatableComponent("otm.gui.android_research")) - button.bind(this::openResearchTree) + stripLeft.dock = Dock.LEFT + stripLeft.dockRight = 3f + + WidePowerGaugePanel(this, stripLeft, menu.powerWidget).also { + it.dock = Dock.TOP + } + + SlotPanel(this, stripLeft, menu.batterySlot).also { + it.dock = Dock.TOP + it.dockTop = 4f + } + + val playerStrip = EditablePanel(this, frame, height = 72f) + playerStrip.dock = Dock.TOP + + val armorStrip = EditablePanel(this, playerStrip, width = AbstractSlotPanel.SIZE) + armorStrip.dock = Dock.LEFT + + for (slot in menu.armorSlots) { + SlotPanel(this, armorStrip, slot).also { it.dock = Dock.TOP } + } + + EntityRendererPanel(this, playerStrip, minecraft!!.player!!).also { + it.dock = Dock.LEFT + } + + val androidStrip = EditablePanel(this, playerStrip, width = AbstractSlotPanel.SIZE) + androidStrip.dock = Dock.LEFT + + SlotPanel(this, androidStrip, menu.androidBattery).also { it.dock = Dock.TOP } + + val preview = makeCanvas(true) + + preview.parent = playerStrip + preview.height = 72f + preview.dock = Dock.TOP + + this.playerStrip = playerStrip return frame } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoSuitInventoryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoSuitInventoryScreen.kt index bebb4b8d0..cac0ee43c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoSuitInventoryScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoSuitInventoryScreen.kt @@ -110,23 +110,7 @@ class ExoSuitInventoryScreen(menu: ExoSuitInventoryMenu) : MatteryScreen, parent: EditablePanel?, @@ -46,7 +52,7 @@ open class EditablePanel @JvmOverloads constructor( field?.onUnParent(this) field = value - needsInvalidation = true + layoutInvalidated = true value?.onParent(this) } @@ -56,8 +62,9 @@ open class EditablePanel @JvmOverloads constructor( val old = field field = value - needsInvalidation = true + // layoutInvalidated = true xPosUpdated(value, old) + parent?.boundsInvalidated = true } var y: Float = 0f @@ -66,8 +73,9 @@ open class EditablePanel @JvmOverloads constructor( val old = field field = value - needsInvalidation = true + // layoutInvalidated = true yPosUpdated(value, old) + parent?.boundsInvalidated = true } var width: Float = 10f @@ -78,8 +86,9 @@ open class EditablePanel @JvmOverloads constructor( val old = field field = value - needsInvalidation = true + layoutInvalidated = true widthUpdated(value, old) + parent?.boundsInvalidated = true } var height: Float = 10f @@ -90,13 +99,33 @@ open class EditablePanel @JvmOverloads constructor( val old = field field = value - needsInvalidation = true + layoutInvalidated = true heightUpdated(value, old) + parent?.boundsInvalidated = true } + /** + * Width to be utilized in docking code. + * + * Can only differ from [width] if [dockResize] is not [DockResizeMode.ALL] and not [DockResizeMode.WIDTH] + */ + var dockedWidth: Float = 0f + private set + + /** + * Height to be utilized in docking code. + * + * Can only differ from [height] if [dockResize] is not [DockResizeMode.ALL] and not [DockResizeMode.HEIGHT] + */ + var dockedHeight: Float = 0f + private set + @JvmField protected val children = ArrayList() - private var needsInvalidation = true + val childrenView: List = Collections.unmodifiableList(children) + + private var layoutInvalidated = true + private var boundsInvalidated = true protected var visibleAsChildren: Boolean = true private set @@ -110,8 +139,8 @@ open class EditablePanel @JvmOverloads constructor( if (old != new) { visibilityChanges(new, old) - parent?.needsInvalidation = true - needsInvalidation = true + parent?.layoutInvalidated = true + layoutInvalidated = true if (new) { killFocus() @@ -130,24 +159,64 @@ open class EditablePanel @JvmOverloads constructor( var scissor = false var dock = Dock.NONE set(value) { - field = value - parent?.needsInvalidation = true + if (field != value) { + field = value + parent?.layoutInvalidated = true + } + } + var dockResize = DockResizeMode.ALL + set(value) { + if (field != value) { + field = value + parent?.layoutInvalidated = true + } } var dockMargin = DockProperty.EMPTY set(value) { if (field != value) { - parent?.needsInvalidation = true + field = value + parent?.layoutInvalidated = true } - - field = value } + + var dockLeft: Float + get() = dockMargin.left + set(value) { + if (value != dockMargin.left) { + dockMargin = dockMargin.copy(left = value) + } + } + + var dockRight: Float + get() = dockMargin.right + set(value) { + if (value != dockMargin.right) { + dockMargin = dockMargin.copy(right = value) + } + } + + var dockTop: Float + get() = dockMargin.top + set(value) { + if (value != dockMargin.top) { + dockMargin = dockMargin.copy(top = value) + } + } + + var dockBottom: Float + get() = dockMargin.bottom + set(value) { + if (value != dockMargin.bottom) { + dockMargin = dockMargin.copy(bottom = value) + } + } + var dockPadding = DockProperty.EMPTY set(value) { if (field != value) { - parent?.needsInvalidation = true + field = value + parent?.layoutInvalidated = true } - - field = value } var acceptMouseInput = true @@ -228,7 +297,7 @@ open class EditablePanel @JvmOverloads constructor( val font: Font get() = screen.font fun invalidateLayout() { - needsInvalidation = true + layoutInvalidated = true } fun most3DHeight(): Float { @@ -252,7 +321,7 @@ open class EditablePanel @JvmOverloads constructor( private fun onParent(child: EditablePanel) { if (children.contains(child)) throw IllegalStateException("Already containing $child") children.add(child) - needsInvalidation = true + layoutInvalidated = true updateVisible() } @@ -260,7 +329,7 @@ open class EditablePanel @JvmOverloads constructor( val indexOf = children.indexOf(child) if (indexOf == -1) throw IllegalStateException("Already not containing $child") children.removeAt(indexOf) - needsInvalidation = true + layoutInvalidated = true updateVisible() } @@ -280,9 +349,10 @@ open class EditablePanel @JvmOverloads constructor( var depth = 0f - if (needsInvalidation) { - needsInvalidation = false + if (layoutInvalidated) { performLayout() + } else if (boundsInvalidated) { + updateBounds() } val parent = this.parent @@ -412,6 +482,8 @@ open class EditablePanel @JvmOverloads constructor( * In almost all cases you want to call [invalidateLayout], and not this. */ open fun performLayout() { + layoutInvalidated = false + var left = dockPadding.left var right = dockPadding.right var top = dockPadding.top @@ -424,57 +496,59 @@ open class EditablePanel @JvmOverloads constructor( Dock.FILL -> {} Dock.LEFT -> { - child.setPos(left + child.dockMargin.left, top + child.dockMargin.top) + child.x = left + child.dockMargin.left + child.y = top + child.dockMargin.top + left += child.width + child.dockMargin.left + child.dockMargin.right - child.height = ( - Math.max( - 1f, - height - top - bottom - child.dockMargin.top - child.dockMargin.bottom - ) - ) + + child.dockedHeight = 1f.coerceAtLeast(height - top - bottom - child.dockMargin.top - child.dockMargin.bottom) + + if (child.dockResize.changeHeight) + child.height = child.dockedHeight + else if (child.height != child.dockedHeight) + child.y += child.dockedHeight / 2f - child.height / 2f } Dock.RIGHT -> { - child.setPos( - width - right - child.width - child.dockMargin.right, - top + child.dockMargin.top - ) + child.x = width - right - child.width - child.dockMargin.right + child.y = top + child.dockMargin.top right += child.width + child.dockMargin.left + child.dockMargin.right - child.height = ( - Math.max( - 1f, - height - top - bottom - child.dockMargin.top - child.dockMargin.bottom - ) - ) + + child.dockedHeight = 1f.coerceAtLeast(height - top - bottom - child.dockMargin.top - child.dockMargin.bottom) + + if (child.dockResize.changeHeight) + child.height = child.dockedHeight + else if (child.height != child.dockedHeight) + child.y += child.dockedHeight / 2f - child.height / 2f } Dock.TOP -> { - child.setPos(left + child.dockMargin.left, top + child.dockMargin.top) + child.x = left + child.dockMargin.left + child.y = top + child.dockMargin.top + top += child.height + child.dockMargin.top + child.dockMargin.bottom - child.width = ( - Math.max( - 1f, - width - left - right - child.dockMargin.left - child.dockMargin.right - ) - ) + child.dockedWidth = 1f.coerceAtLeast(width - left - right - child.dockMargin.left - child.dockMargin.right) + + if (child.dockResize.changeWidth) + child.width = child.dockedWidth + else if (child.width != child.dockedWidth) + child.x += child.dockedWidth / 2f - child.width / 2f } Dock.BOTTOM -> { - child.setPos( - left + child.dockMargin.left, - height - bottom - child.height - child.dockMargin.bottom - ) + child.x = left + child.dockMargin.left + child.y = height - bottom - child.height - child.dockMargin.bottom bottom += child.height + child.dockMargin.top + child.dockMargin.bottom - child.width = ( - Math.max( - 1f, - width - left - right - child.dockMargin.left - child.dockMargin.right - ) - ) + child.dockedWidth = 1f.coerceAtLeast(width - left - right - child.dockMargin.left - child.dockMargin.right) + + if (child.dockResize.changeWidth) + child.width = child.dockedWidth + else if (child.width != child.dockedWidth) + child.x += child.dockedWidth / 2f - child.width / 2f } } } @@ -489,15 +563,30 @@ open class EditablePanel @JvmOverloads constructor( LOGGER.error("Unable to fit $child inside $this (FILL returned dimensions of $w $h)") } - child.setDimensions( - left + child.dockMargin.left, - top + child.dockMargin.top, - Math.max(1f, w), - Math.max(1f, h) - ) + child.x = left + child.dockMargin.left + child.y = top + child.dockMargin.top + + child.dockedWidth = w.coerceAtLeast(1f) + child.dockedHeight = h.coerceAtLeast(1f) + + if (child.dockResize.changeHeight) + child.height = child.dockedHeight + else if (child.height != child.dockedHeight) + child.y += child.dockedHeight / 2f - child.height / 2f + + if (child.dockResize.changeWidth) + child.width = child.dockedWidth + else if (child.width != child.dockedWidth) + child.x += child.dockedWidth / 2f - child.width / 2f } } + updateBounds() + } + + fun updateBounds() { + boundsInvalidated = false + boundingX = 0f boundingY = 0f boundingWidth = 0f @@ -513,6 +602,30 @@ open class EditablePanel @JvmOverloads constructor( } } + /** + * Attempts to tightly fit dimensions to all children + * + * Performs layout if required + */ + open fun sizeToContents() { + if (layoutInvalidated) { + performLayout() + } + + var width = 1f + var height = 1f + + for (child in children) { + if (child.isVisible()) { + width = width.coerceAtLeast(child.x + child.width) + height = height.coerceAtLeast(child.y + child.height) + } + } + + this.width = width + this.height = height + } + companion object { private val LOGGER = LogManager.getLogger()!! } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt new file mode 100644 index 000000000..b55ef85a0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt @@ -0,0 +1,58 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import net.minecraft.client.gui.screens.inventory.InventoryScreen +import net.minecraft.world.entity.LivingEntity +import ru.dbotthepony.mc.otm.client.render.element +import ru.dbotthepony.mc.otm.client.screen.ExoSuitInventoryScreen +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen + +private fun calculateScale(width: Float, height: Float): Int { + val aspectRatio = width / height + + if (aspectRatio.isNaN()) { + return 1 + } + + if (aspectRatio < 0.7083) { + return (width / 51f * 30f).toInt() + } else { + return (height / 72f * 30f).toInt() + } +} + +class EntityRendererPanel( + screen: MatteryScreen<*>, + parent: EditablePanel?, + val entity: LivingEntity, + x: Float = 0f, + y: Float = 0f, + width: Float = ENTITY_RECTANGLE.w, + height: Float = ENTITY_RECTANGLE.h, + var renderScale: Int = calculateScale(width, height) +) : EditablePanel(screen, parent, x, y, width, height) { + override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + ExoSuitInventoryScreen.ENTITY_RECTANGLE.render(stack) + + if (entity.isDeadOrDying) { + return + } + + val renderX = absoluteX.toInt() + width.toInt() / 2 + val renderY = absoluteY.toInt() + (height * 0.9f).toInt() + + InventoryScreen.renderEntityInInventory( + renderX, + renderY, + renderScale, + renderX - mouseX, + absoluteY + height * 0.15f - mouseY, + entity + ) + } + + companion object { + val ENTITY_RECTANGLE = AbstractContainerScreen.INVENTORY_LOCATION.element(25f, 7f, 51f, 72f) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/AndroidStationMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/AndroidStationMenu.kt index 2ca3cfb30..5d57d8e08 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/AndroidStationMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/AndroidStationMenu.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.menu +import com.google.common.collect.ImmutableList import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player @@ -38,7 +39,10 @@ class AndroidStationMenu @JvmOverloads constructor( override val storageSlots = listOf(addSlot(androidBattery)) + val armorSlots = makeArmorSlots() + init { + armorSlots.forEach(this::addSlot) addInventorySlots() } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoSuitInventoryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoSuitInventoryMenu.kt index d8e0fec45..68fb22471 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoSuitInventoryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoSuitInventoryMenu.kt @@ -48,12 +48,7 @@ class ExoSuitInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMen mainInventorySlots = builder.build() } - val armorSlots: List = ImmutableList.of( - EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.HEAD), - EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.CHEST), - EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.LEGS), - EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.FEET), - ) + val armorSlots: List = makeArmorSlots() val offhandSlot = object : InventorySlot(capability.ply.inventory, 40) { override fun getNoItemIcon(): Pair { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt index 0f488284c..d53237188 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -372,6 +372,13 @@ abstract class MatteryMenu @JvmOverloads protected constructor( super.addDataSlots(p_38885_) } + fun makeArmorSlots(): List = ImmutableList.of( + EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.HEAD), + EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.CHEST), + EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.LEGS), + EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.FEET), + ) + companion object { val TEXTURE_EMPTY_SLOTS: List = ImmutableList.of( InventoryMenu.EMPTY_ARMOR_SLOT_BOOTS, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt index de00f1b22..6ae9831af 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt @@ -12,7 +12,9 @@ class LevelGaugeWidget(menu: MatteryMenu) : AbstractWidget(menu) { var maxLevel = {ImpreciseFraction.ONE } var levelContainer by menu.mSynchronizer.fraction() + private set var maxLevelContainer by menu.mSynchronizer.fraction() + private set constructor( menu: MatteryMenu,