@@ -26,6 +26,7 @@ import gg.essential.universal.UResolution
2626import org.lwjgl.opengl.GL11
2727import java.awt.Color
2828import java.util.*
29+ import java.util.concurrent.ConcurrentHashMap
2930import java.util.concurrent.ConcurrentLinkedDeque
3031import java.util.concurrent.CopyOnWriteArrayList
3132import java.util.function.BiConsumer
@@ -56,6 +57,7 @@ abstract class UIComponent : Observable(), ReferenceHolder {
5657 val effects: MutableList <Effect > = mutableListOf<Effect >().observable().apply {
5758 addObserver { _, event ->
5859 updateUpdateFuncsOnChangedEffect(event)
60+ updateEffectFlagsOnChangedEffect(event)
5961 }
6062 }
6163
@@ -66,6 +68,7 @@ abstract class UIComponent : Observable(), ReferenceHolder {
6668 setWindowCacheOnChangedChild(event)
6769 updateFloatingComponentsOnChangedChild(event)
6870 updateUpdateFuncsOnChangedChild(event)
71+ updateCombinedFlagsOnChangedChild(event)
6972 }
7073 }
7174
@@ -81,8 +84,14 @@ abstract class UIComponent : Observable(), ReferenceHolder {
8184 notifyObservers(constraints)
8285 }
8386
84- var lastDraggedMouseX: Double? = null
85- var lastDraggedMouseY: Double? = null
87+ @Deprecated(" This property should have been private and probably does not do what you expect it to." )
88+ var lastDraggedMouseX: Double?
89+ get() = Window .ofOrNull(this )?.prevDraggedMouseX?.toDouble()
90+ set(_) {}
91+ @Deprecated(" This property should have been private and probably does not do what you expect it to." )
92+ var lastDraggedMouseY: Double?
93+ get() = Window .ofOrNull(this )?.prevDraggedMouseY?.toDouble()
94+ set(_) {}
8695
8796 /* Bubbling Events */
8897 var mouseScrollListeners = mutableListOf<UIComponent .(UIScrollEvent ) - > Unit > ()
@@ -93,8 +102,11 @@ abstract class UIComponent : Observable(), ReferenceHolder {
93102 /* Non-Bubbling Events */
94103 val mouseReleaseListeners = mutableListOf<UIComponent .() - > Unit > ()
95104 val mouseEnterListeners = mutableListOf<UIComponent .() - > Unit > ()
105+ get() = field.also { ownFlags + = Flags .RequiresMouseMove }
96106 val mouseLeaveListeners = mutableListOf<UIComponent .() - > Unit > ()
107+ get() = field.also { ownFlags + = Flags .RequiresMouseMove }
97108 val mouseDragListeners = mutableListOf<UIComponent .(mouseX: Float , mouseY: Float , button: Int ) - > Unit > ()
109+ get() = field.also { ownFlags + = Flags .RequiresMouseDrag }
98110 val keyTypedListeners = mutableListOf<UIComponent .(typedChar: Char , keyCode: Int ) - > Unit > ()
99111
100112 private var currentlyHovered = false
@@ -143,6 +155,73 @@ abstract class UIComponent : Observable(), ReferenceHolder {
143155 children.forEach { it.recursivelySetWindowCache(window) }
144156 }
145157
158+ // region Internal flags tracking
159+ /* * Flags which apply to this component specifically. */
160+ internal var ownFlags = Flags .initialFor(javaClass)
161+ set(newValue) {
162+ val oldValue = field
163+ if (oldValue == newValue) return
164+ field = newValue
165+ if (oldValue in newValue) { // merely additions?
166+ combinedFlags + = newValue
167+ } else {
168+ recomputeCombinedFlags()
169+ }
170+ }
171+ /* * Flags which apply to one of the effects of tis component. */
172+ internal var effectFlags = Flags (0u )
173+ set(newValue) {
174+ val oldValue = field
175+ if (oldValue == newValue) return
176+ field = newValue
177+ if (oldValue in newValue) { // merely additions?
178+ combinedFlags + = newValue
179+ } else {
180+ recomputeCombinedFlags()
181+ }
182+ }
183+ /* * Combined flags of this component, its effects, and its children. */
184+ internal var combinedFlags = ownFlags
185+ set(newValue) {
186+ val oldValue = field
187+ if (oldValue == newValue) return
188+ field = newValue
189+ if (hasParent && parent != this ) {
190+ if (oldValue in newValue) { // merely additions?
191+ parent.combinedFlags + = newValue
192+ } else {
193+ parent.recomputeCombinedFlags()
194+ }
195+ }
196+ }
197+
198+ internal fun recomputeEffectFlags () {
199+ effectFlags = effects.fold(Flags (0u )) { acc, effect -> acc + effect.flags }
200+ }
201+
202+ private fun recomputeCombinedFlags () {
203+ combinedFlags = children.fold(ownFlags + effectFlags) { acc, child -> acc + child.combinedFlags }
204+ }
205+
206+ private fun updateEffectFlagsOnChangedEffect (possibleEvent : Any ) {
207+ @Suppress(" UNCHECKED_CAST" )
208+ when (val event = possibleEvent as ? ObservableListEvent <Effect > ? : return ) {
209+ is ObservableAddEvent -> effectFlags + = event.element.value.flags
210+ is ObservableRemoveEvent -> recomputeEffectFlags()
211+ is ObservableClearEvent -> recomputeEffectFlags()
212+ }
213+ }
214+
215+ private fun updateCombinedFlagsOnChangedChild (possibleEvent : Any ) {
216+ @Suppress(" UNCHECKED_CAST" )
217+ when (val event = possibleEvent as ? ObservableListEvent <UIComponent > ? : return ) {
218+ is ObservableAddEvent -> combinedFlags + = event.element.value.combinedFlags
219+ is ObservableRemoveEvent -> recomputeCombinedFlags()
220+ is ObservableClearEvent -> recomputeCombinedFlags()
221+ }
222+ }
223+ // endregion
224+
146225 protected fun requireChildrenUnlocked () {
147226 requireState(childrenLocked == 0 , " Cannot modify children while iterating over them." )
148227 }
@@ -564,6 +643,16 @@ abstract class UIComponent : Observable(), ReferenceHolder {
564643 fun beforeChildrenDrawCompat (matrixStack : UMatrixStack ) = UMatrixStack .Compat .runLegacyMethod(matrixStack) { beforeChildrenDraw() }
565644
566645 open fun mouseMove (window : Window ) {
646+ if (Flags .RequiresMouseMove in ownFlags) {
647+ updateCurrentlyHoveredState(window)
648+ }
649+
650+ if (Flags .RequiresMouseMove in combinedFlags) {
651+ this .forEachChild { it.mouseMove(window) }
652+ }
653+ }
654+
655+ private fun updateCurrentlyHoveredState (window : Window ) {
567656 val hovered = isHovered() && window.hoveredFloatingComponent.let {
568657 it == null || it == this || isComponentInParentChain(it)
569658 }
@@ -577,8 +666,6 @@ abstract class UIComponent : Observable(), ReferenceHolder {
577666 this .listener()
578667 currentlyHovered = false
579668 }
580-
581- this .forEachChild { it.mouseMove(window) }
582669 }
583670
584671 /* *
@@ -589,8 +676,6 @@ abstract class UIComponent : Observable(), ReferenceHolder {
589676 open fun mouseClick (mouseX : Double , mouseY : Double , button : Int ) {
590677 val clicked = hitTest(mouseX.toFloat(), mouseY.toFloat())
591678
592- lastDraggedMouseX = mouseX
593- lastDraggedMouseY = mouseY
594679 lastClickCount = if (System .currentTimeMillis() - lastClickTime < 500 ) lastClickCount + 1 else 1
595680 lastClickTime = System .currentTimeMillis()
596681
@@ -627,9 +712,6 @@ abstract class UIComponent : Observable(), ReferenceHolder {
627712 for (listener in mouseReleaseListeners)
628713 this .listener()
629714
630- lastDraggedMouseX = null
631- lastDraggedMouseY = null
632-
633715 this .forEachChild { it.mouseRelease() }
634716 }
635717
@@ -708,17 +790,17 @@ abstract class UIComponent : Observable(), ReferenceHolder {
708790 }
709791
710792 private inline fun doDragMouse (mouseX : Float , mouseY : Float , button : Int , superCall : UIComponent .() -> Unit ) {
711- if (lastDraggedMouseX == mouseX.toDouble() && lastDraggedMouseY == mouseY.toDouble())
793+ if (Flags . RequiresMouseDrag !in combinedFlags) {
712794 return
795+ }
713796
714- lastDraggedMouseX = mouseX.toDouble()
715- lastDraggedMouseY = mouseY.toDouble()
716-
717- val relativeX = mouseX - getLeft()
718- val relativeY = mouseY - getTop()
797+ if (Flags .RequiresMouseDrag in ownFlags) {
798+ val relativeX = mouseX - getLeft()
799+ val relativeY = mouseY - getTop()
719800
720- for (listener in mouseDragListeners)
721- this .listener(relativeX, relativeY, button)
801+ for (listener in mouseDragListeners)
802+ this .listener(relativeX, relativeY, button)
803+ }
722804
723805 this .forEachChild { it.superCall() }
724806 }
@@ -1540,6 +1622,47 @@ abstract class UIComponent : Observable(), ReferenceHolder {
15401622 return { heldReferences.remove(listener) }
15411623 }
15421624
1625+ @JvmInline
1626+ internal value class Flags (val bits : UInt ) {
1627+ operator fun contains (element : Flags ) = this .bits and element.bits == element.bits
1628+ infix fun and (other : Flags ) = Flags (this .bits and other.bits)
1629+ infix fun or (other : Flags ) = Flags (this .bits or other.bits)
1630+ operator fun plus (other : Flags ) = this or other
1631+ operator fun minus (other : Flags ) = Flags (this .bits and other.bits.inv ())
1632+ fun inv () = Flags (bits.inv () and All .bits)
1633+
1634+ companion object {
1635+ private var nextBit = 0
1636+ private val iota: Flags
1637+ get() = Flags (1u shl nextBit++ )
1638+
1639+ val None = Flags (0u )
1640+
1641+ val RequiresMouseMove = iota
1642+ val RequiresMouseDrag = iota
1643+
1644+ val All = Flags (iota.bits - 1u )
1645+
1646+ private val cache = ConcurrentHashMap <Class <* >, Flags > ().apply {
1647+ put(Effect ::class .java, Flags (0u ))
1648+ put(UIComponent ::class .java, Flags (0u ))
1649+ put(Window ::class .java, Flags (0u ))
1650+ }
1651+ fun initialFor (cls : Class <* >): Flags = cache.getOrPut(cls) {
1652+ flagsBasedOnOverrides(cls) + initialFor(cls.superclass)
1653+ }
1654+
1655+ private fun flagsBasedOnOverrides (cls : Class <* >): Flags = listOf (
1656+ if (cls.overridesMethod(" mouseMove" , Window ::class .java)) RequiresMouseMove else None ,
1657+ if (cls.overridesMethod(" dragMouse" , Int ::class .java, Int ::class .java, Int ::class .java)) RequiresMouseDrag else None ,
1658+ if (cls.overridesMethod(" dragMouse" , Float ::class .java, Float ::class .java, Int ::class .java)) RequiresMouseDrag else None ,
1659+ ).reduce { acc, flags -> acc + flags }
1660+
1661+ private fun Class <* >.overridesMethod (name : String , vararg args : Class <* >) =
1662+ try { getDeclaredMethod(name, * args); true } catch (_: NoSuchMethodException ) { false }
1663+ }
1664+ }
1665+
15431666 companion object {
15441667 // Default value for componentName used as marker for lazy init.
15451668 private val defaultComponentName = String ()
0 commit comments