From aa461c322f3d22fd3f8d66eaae4bb09128e62387 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Mon, 2 Sep 2024 19:31:56 +0700 Subject: [PATCH] Use CopyOnWrite for panel lists, update panel isHovered to be more reliable, fix FoldableSlotPanel --- .../mc/otm/client/screen/MatteryScreen.kt | 34 ++--- .../otm/client/screen/panels/EditablePanel.kt | 119 +++++++++++++----- .../otm/client/screen/panels/Panel2Widget.kt | 4 +- .../screen/panels/slot/FoldableSlotPanel.kt | 6 +- 4 files changed, 112 insertions(+), 51 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt index 3c4bcc0cf..6ce5570d5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt @@ -3,7 +3,6 @@ package ru.dbotthepony.mc.otm.client.screen import com.mojang.blaze3d.systems.RenderSystem import it.unimi.dsi.fastutil.ints.Int2ObjectFunction import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap -import it.unimi.dsi.fastutil.objects.ObjectArrayList import net.minecraft.ChatFormatting import net.minecraft.client.gui.Font import net.minecraft.client.gui.GuiGraphics @@ -54,8 +53,7 @@ import ru.dbotthepony.mc.otm.menu.MatterySlot import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import java.util.* -import kotlin.collections.ArrayDeque -import kotlin.collections.ArrayList +import java.util.concurrent.CopyOnWriteArrayList import kotlin.collections.List import kotlin.collections.MutableSet import kotlin.collections.isNotEmpty @@ -73,7 +71,8 @@ import kotlin.collections.withIndex abstract class MatteryScreen(menu: T, inventory: Inventory, title: Component) : AbstractContainerScreen(menu, inventory, title) { constructor(menu: T, title: Component) : this(menu, menu.inventory, title) - protected val panels = ArrayDeque>() + protected val panels = CopyOnWriteArrayList>() + protected val panelsReversed = panels.asReversed() val panelsView: List> = Collections.unmodifiableList(panels) var inventoryFrame: FramePanel>? = null @@ -473,7 +472,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit override fun onClose() { super.onClose() - ObjectArrayList(panels).forEach { it.markRemoved() } + panels.forEach { it.markRemoved() } } public override fun recalculateQuickCraftRemaining() { @@ -508,7 +507,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit var click = false var focusKilled = false - for (panel in ObjectArrayList(panels)) { + for (panel in panels) { if (click || !panel.mouseClickedChecked(x, y, button)) { focusKilled = panel.killFocus() || focusKilled } else { @@ -527,7 +526,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit private var lastDragSlot: Slot? = null override fun mouseDragged(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { - for (panel in ObjectArrayList(panels)) { + for (panel in panels) { if (panel.mouseDraggedChecked(x, y, button, xDelta, yDelta)) { if (returnSlot != null) { super.mouseDragged(x, y, button, xDelta, yDelta) @@ -555,7 +554,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } } - for (panel in ObjectArrayList(panels)) { + for (panel in panels) { if (panel.mouseReleasedChecked(p_97812_, p_97813_, p_97814_)) { if (returnSlot != null) { super.mouseReleased(p_97812_, p_97813_, p_97814_) @@ -571,7 +570,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean { - for (panel in ObjectArrayList(panels)) { + for (panel in panels) { if (panel.mouseScrolledChecked(mouseX, mouseY, scrollY)) { return true } @@ -581,7 +580,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } override fun keyReleased(p_94715_: Int, p_94716_: Int, p_94717_: Int): Boolean { - for (panel in ObjectArrayList(panels)) { + for (panel in panels) { if (panel.keyReleased(p_94715_, p_94716_, p_94717_)) { return true } @@ -591,7 +590,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } override fun charTyped(p_94683_: Char, p_94684_: Int): Boolean { - for (panel in ObjectArrayList(panels)) { + for (panel in panels) { if (panel.charTyped(p_94683_, p_94684_)) { return true } @@ -601,7 +600,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } override fun keyPressed(key: Int, scancode: Int, mods: Int): Boolean { - for (panel in ObjectArrayList(panels)) { + for (panel in panels) { if (panel.keyPressed(key, scancode, mods)) { if (returnSlot != null) { super.keyPressed(key, scancode, mods) @@ -662,23 +661,26 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit for (panel in panels) { if (hovered) { panel.unsetHovered() - } else if (panel.tickHover(mouseXf, mouseYf)) { + } else if (panel.tickHovered0(mouseXf, mouseYf)) { hovered = true } } + panels.forEach { it.tickHovered1() } + panels.forEach { it.tickHovered2() } + RenderSystem.defaultBlendFunc() RenderSystem.enableBlend() RenderSystem.enableDepthTest() - for (panel in panels.asReversed()) { + for (panel in panelsReversed) { RenderSystem.depthFunc(GL11.GL_ALWAYS) RenderSystem.setShaderColor(1f, 1f, 1f, 1f) panel.render(wrap, mouseXf, mouseYf, partialTick) } - if (!panels.asReversed().any { it.updateCursor0() }) - panels.asReversed().any { it.updateCursor1() } + if (!panelsReversed.any { it.updateCursor0() }) + panelsReversed.any { it.updateCursor1() } RenderSystem.depthFunc(GL11.GL_LESS) NeoForge.EVENT_BUS.post(ContainerScreenEvent.Render.Background(this, graphics, mouseX, mouseY)) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt index 4b5ed150f..57e16b586 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt @@ -13,7 +13,6 @@ import net.minecraft.client.gui.navigation.ScreenRectangle import net.minecraft.client.gui.screens.Screen import net.minecraft.client.renderer.Rect2i import net.minecraft.network.chat.Component -import net.minecraft.world.inventory.Slot import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.SystemTime import ru.dbotthepony.mc.otm.client.CursorType @@ -28,6 +27,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.input.QueryUserPanel import ru.dbotthepony.mc.otm.core.collect.concatIterators import ru.dbotthepony.mc.otm.core.collect.flatMap import java.util.* +import java.util.concurrent.CopyOnWriteArrayList import java.util.function.Predicate import kotlin.collections.ArrayList import kotlin.math.roundToInt @@ -259,8 +259,8 @@ open class EditablePanel( var dockedHeight: Float = 0f private set - private val childrenInternal = ArrayList>() - private val visibleChildrenInternal = ArrayList>() + private val childrenInternal = CopyOnWriteArrayList>() + private val visibleChildrenInternal = CopyOnWriteArrayList>() val children: List> = Collections.unmodifiableList(childrenInternal) val visibleChildren: List> = Collections.unmodifiableList(visibleChildrenInternal) @@ -659,47 +659,54 @@ open class EditablePanel( var absoluteY = 0f private set + private var pendingIsHovered = false + private set(value) { + if (field != value) { + field = value + pendingIsEffectivelyHovered = value || pendingIsEffectivelyHovered + parent?.updateIsEffectivelyHovered(value || pendingIsEffectivelyHovered) + } + } + + private var pendingIsEffectivelyHovered = false + private set(value) { + if (field != value) { + field = value + parent?.updateIsEffectivelyHovered(value || pendingIsHovered) + } + } + /** * If this exact panel is hovered. Panel is not considered hovered if any of its children are hovered */ var isHovered = false - private set(value) { - if (value == field) { - return - } - - val old = field - field = value - - onHoverUpdate(old, value) - } + private set /** * If this panel or any of its children are hovered */ - val isEverHovered: Boolean get() { - return isHovered || visibleChildrenInternal.any { it.isEverHovered } + var isEffectivelyHovered: Boolean = false + private set + + private fun updateIsEffectivelyHovered(childrenState: Boolean) { + pendingIsEffectivelyHovered = pendingIsHovered || childrenState || visibleChildrenInternal.any { it.pendingIsEffectivelyHovered } } fun unsetHovered() { - isHovered = false + pendingIsHovered = false + pendingIsEffectivelyHovered = false for (child in childrenInternal) { child.unsetHovered() } } - protected open fun onHoverUpdate(oldHover: Boolean, newHover: Boolean) { - if (newHover) { - onHovered() - } else { - onUnHovered() - } - } - protected open fun onHovered() {} protected open fun onUnHovered() {} + protected open fun onEffectivelyHovered() {} + protected open fun onEffectivelyUnHovered() {} + var hasFocusedChildren = false private set(value) { if (field != value) { @@ -746,6 +753,7 @@ open class EditablePanel( if (child.visible) { visibleChildrenInternal.add(child) + updateIsEffectivelyHovered(false) layoutInvalidated = true } @@ -793,7 +801,7 @@ open class EditablePanel( } protected open fun shouldRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { - return isEverHovered || isGrabbingMouseInput() + return isEffectivelyHovered || isGrabbingMouseInput() } fun isVisible(): Boolean { @@ -934,7 +942,10 @@ open class EditablePanel( } } - fun tickHover(mouseX: Float, mouseY: Float): Boolean { + /** + * Determining whenever panel is hovered or not + */ + fun tickHovered0(mouseX: Float, mouseY: Float): Boolean { if (isRemoved) return false @@ -957,23 +968,69 @@ open class EditablePanel( for (child in visibleChildrenInternal) { if (hit) { child.unsetHovered() - } else if (child.tickHover(mouseX, mouseY)) { + } else if (child.tickHovered0(mouseX, mouseY)) { hit = true } } - isHovered = + pendingIsHovered = !hit && mouseX in absoluteX ..< absoluteX + width && mouseY in absoluteY ..< absoluteY + height - return hit || isHovered + return hit || pendingIsHovered } else { unsetHovered() return false } } + private var needToCallIsHoveredUpdate = false + private var needToCallIsEffectivelyHoveredUpdate = false + + /** + * Updating publicly-visible flags regarding hovered + */ + fun tickHovered1() { + if (isRemoved) + return + + needToCallIsHoveredUpdate = isHovered != pendingIsHovered + needToCallIsEffectivelyHoveredUpdate = isEffectivelyHovered != pendingIsEffectivelyHovered + + isHovered = pendingIsHovered + isEffectivelyHovered = pendingIsEffectivelyHovered + + visibleChildrenInternal.forEach { it.tickHovered1() } + } + + /** + * Dispatching "is hovered" update callbacks + */ + fun tickHovered2() { + if (needToCallIsHoveredUpdate) { + if (isHovered) { + onHovered() + } else { + onUnHovered() + } + + needToCallIsHoveredUpdate = false + } + + if (needToCallIsEffectivelyHoveredUpdate) { + if (isEffectivelyHovered) { + onEffectivelyHovered() + } else { + onEffectivelyUnHovered() + } + + needToCallIsEffectivelyHoveredUpdate = false + } + + visibleChildrenInternal.forEach { it.tickHovered2() } + } + data class PanelOfTypeResult(val panel: T?, val interrupt: Boolean) { constructor(panel: T) : this(panel, true) @@ -1765,7 +1822,7 @@ open class EditablePanel( tickInner() - for (child in Array(visibleChildrenInternal.size) { visibleChildrenInternal[it] }) { + for (child in visibleChildrenInternal) { child.tick() } } @@ -1796,7 +1853,7 @@ open class EditablePanel( screen.removePanel(this as EditablePanel>) } - for (child in Array(childrenInternal.size) { childrenInternal[it] }) { + for (child in childrenInternal) { child.remove() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Panel2Widget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Panel2Widget.kt index e4664f2bd..3793a24fc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Panel2Widget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Panel2Widget.kt @@ -22,7 +22,9 @@ class Panel2Widget>( val wrap = MGUIGraphics(graphics) - panel.tickHover(xFloat, yFloat) + panel.tickHovered0(xFloat, yFloat) + panel.tickHovered1() + panel.tickHovered2() panel.render(wrap, xFloat, yFloat, partialTick) panel.renderTooltips(wrap, xFloat, yFloat, partialTick) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FoldableSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FoldableSlotPanel.kt index f5dda62e4..8af12c5cd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FoldableSlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FoldableSlotPanel.kt @@ -59,7 +59,7 @@ open class FoldableSlotPanel, out T : Slot>( this@FoldableSlotPanel.hoverPanel = null } - override fun onUnHovered() { + override fun onEffectivelyUnHovered() { remove() } @@ -76,11 +76,11 @@ open class FoldableSlotPanel, out T : Slot>( var hoverPanel: FoldableSlotPanel<*, *>.HoverPanel? = null protected set - override fun onHovered() { + override fun onEffectivelyHovered() { hoveringSince = SystemTime() } - override fun onUnHovered() { + override fun onEffectivelyUnHovered() { hoveringSince = null }