Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 140 additions & 17 deletions src/main/kotlin/gg/essential/elementa/UIComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import gg.essential.universal.UResolution
import org.lwjgl.opengl.GL11
import java.awt.Color
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.CopyOnWriteArrayList
import java.util.function.BiConsumer
Expand Down Expand Up @@ -55,6 +56,7 @@ abstract class UIComponent : Observable(), ReferenceHolder {
val effects: MutableList<Effect> = mutableListOf<Effect>().observable().apply {
addObserver { _, event ->
updateUpdateFuncsOnChangedEffect(event)
updateEffectFlagsOnChangedEffect(event)
}
}

Expand All @@ -65,6 +67,7 @@ abstract class UIComponent : Observable(), ReferenceHolder {
setWindowCacheOnChangedChild(event)
updateFloatingComponentsOnChangedChild(event)
updateUpdateFuncsOnChangedChild(event)
updateCombinedFlagsOnChangedChild(event)
}
}

Expand All @@ -80,8 +83,14 @@ abstract class UIComponent : Observable(), ReferenceHolder {
notifyObservers(constraints)
}

var lastDraggedMouseX: Double? = null
var lastDraggedMouseY: Double? = null
@Deprecated("This property should have been private and probably does not do what you expect it to.")
var lastDraggedMouseX: Double?
get() = Window.ofOrNull(this)?.prevDraggedMouseX?.toDouble()
set(_) {}
@Deprecated("This property should have been private and probably does not do what you expect it to.")
var lastDraggedMouseY: Double?
get() = Window.ofOrNull(this)?.prevDraggedMouseY?.toDouble()
set(_) {}

/* Bubbling Events */
var mouseScrollListeners = mutableListOf<UIComponent.(UIScrollEvent) -> Unit>()
Expand All @@ -92,8 +101,11 @@ abstract class UIComponent : Observable(), ReferenceHolder {
/* Non-Bubbling Events */
val mouseReleaseListeners = mutableListOf<UIComponent.() -> Unit>()
val mouseEnterListeners = mutableListOf<UIComponent.() -> Unit>()
get() = field.also { ownFlags += Flags.RequiresMouseMove }
val mouseLeaveListeners = mutableListOf<UIComponent.() -> Unit>()
get() = field.also { ownFlags += Flags.RequiresMouseMove }
val mouseDragListeners = mutableListOf<UIComponent.(mouseX: Float, mouseY: Float, button: Int) -> Unit>()
get() = field.also { ownFlags += Flags.RequiresMouseDrag }
val keyTypedListeners = mutableListOf<UIComponent.(typedChar: Char, keyCode: Int) -> Unit>()

private var currentlyHovered = false
Expand Down Expand Up @@ -142,6 +154,73 @@ abstract class UIComponent : Observable(), ReferenceHolder {
children.forEach { it.recursivelySetWindowCache(window) }
}

//region Internal flags tracking
/** Flags which apply to this component specifically. */
internal var ownFlags = Flags.initialFor(javaClass)
set(newValue) {
val oldValue = field
if (oldValue == newValue) return
field = newValue
if (oldValue in newValue) { // merely additions?
combinedFlags += newValue
} else {
recomputeCombinedFlags()
}
}
/** Flags which apply to one of the effects of tis component. */
internal var effectFlags = Flags(0u)
set(newValue) {
val oldValue = field
if (oldValue == newValue) return
field = newValue
if (oldValue in newValue) { // merely additions?
combinedFlags += newValue
} else {
recomputeCombinedFlags()
}
}
/** Combined flags of this component, its effects, and its children. */
internal var combinedFlags = ownFlags
set(newValue) {
val oldValue = field
if (oldValue == newValue) return
field = newValue
if (hasParent && parent != this) {
if (oldValue in newValue) { // merely additions?
parent.combinedFlags += newValue
} else {
parent.recomputeCombinedFlags()
}
}
}

internal fun recomputeEffectFlags() {
effectFlags = effects.fold(Flags(0u)) { acc, effect -> acc + effect.flags }
}

private fun recomputeCombinedFlags() {
combinedFlags = children.fold(ownFlags + effectFlags) { acc, child -> acc + child.combinedFlags }
}

private fun updateEffectFlagsOnChangedEffect(possibleEvent: Any) {
@Suppress("UNCHECKED_CAST")
when (val event = possibleEvent as? ObservableListEvent<Effect> ?: return) {
is ObservableAddEvent -> effectFlags += event.element.value.flags
is ObservableRemoveEvent -> recomputeEffectFlags()
is ObservableClearEvent -> recomputeEffectFlags()
}
}

private fun updateCombinedFlagsOnChangedChild(possibleEvent: Any) {
@Suppress("UNCHECKED_CAST")
when (val event = possibleEvent as? ObservableListEvent<UIComponent> ?: return) {
is ObservableAddEvent -> combinedFlags += event.element.value.combinedFlags
is ObservableRemoveEvent -> recomputeCombinedFlags()
is ObservableClearEvent -> recomputeCombinedFlags()
}
}
//endregion

protected fun requireChildrenUnlocked() {
requireState(childrenLocked == 0, "Cannot modify children while iterating over them.")
}
Expand Down Expand Up @@ -563,6 +642,16 @@ abstract class UIComponent : Observable(), ReferenceHolder {
fun beforeChildrenDrawCompat(matrixStack: UMatrixStack) = UMatrixStack.Compat.runLegacyMethod(matrixStack) { beforeChildrenDraw() }

open fun mouseMove(window: Window) {
if (Flags.RequiresMouseMove in ownFlags) {
updateCurrentlyHoveredState(window)
}

if (Flags.RequiresMouseMove in combinedFlags) {
this.forEachChild { it.mouseMove(window) }
}
}

private fun updateCurrentlyHoveredState(window: Window) {
val hovered = isHovered() && window.hoveredFloatingComponent.let {
it == null || it == this || isComponentInParentChain(it)
}
Expand All @@ -576,8 +665,6 @@ abstract class UIComponent : Observable(), ReferenceHolder {
this.listener()
currentlyHovered = false
}

this.forEachChild { it.mouseMove(window) }
}

/**
Expand All @@ -588,8 +675,6 @@ abstract class UIComponent : Observable(), ReferenceHolder {
open fun mouseClick(mouseX: Double, mouseY: Double, button: Int) {
val clicked = hitTest(mouseX.toFloat(), mouseY.toFloat())

lastDraggedMouseX = mouseX
lastDraggedMouseY = mouseY
lastClickCount = if (System.currentTimeMillis() - lastClickTime < 500) lastClickCount + 1 else 1
lastClickTime = System.currentTimeMillis()

Expand Down Expand Up @@ -626,9 +711,6 @@ abstract class UIComponent : Observable(), ReferenceHolder {
for (listener in mouseReleaseListeners)
this.listener()

lastDraggedMouseX = null
lastDraggedMouseY = null

this.forEachChild { it.mouseRelease() }
}

Expand Down Expand Up @@ -707,17 +789,17 @@ abstract class UIComponent : Observable(), ReferenceHolder {
}

private inline fun doDragMouse(mouseX: Float, mouseY: Float, button: Int, superCall: UIComponent.() -> Unit) {
if (lastDraggedMouseX == mouseX.toDouble() && lastDraggedMouseY == mouseY.toDouble())
if (Flags.RequiresMouseDrag !in combinedFlags) {
return
}

lastDraggedMouseX = mouseX.toDouble()
lastDraggedMouseY = mouseY.toDouble()

val relativeX = mouseX - getLeft()
val relativeY = mouseY - getTop()
if (Flags.RequiresMouseDrag in ownFlags) {
val relativeX = mouseX - getLeft()
val relativeY = mouseY - getTop()

for (listener in mouseDragListeners)
this.listener(relativeX, relativeY, button)
for (listener in mouseDragListeners)
this.listener(relativeX, relativeY, button)
}

this.forEachChild { it.superCall() }
}
Expand Down Expand Up @@ -1520,6 +1602,47 @@ abstract class UIComponent : Observable(), ReferenceHolder {
return { heldReferences.remove(listener) }
}

@JvmInline
internal value class Flags(val bits: UInt) {
operator fun contains(element: Flags) = this.bits and element.bits == element.bits
infix fun and(other: Flags) = Flags(this.bits and other.bits)
infix fun or(other: Flags) = Flags(this.bits or other.bits)
operator fun plus(other: Flags) = this or other
operator fun minus(other: Flags) = Flags(this.bits and other.bits.inv())
fun inv() = Flags(bits.inv() and All.bits)

companion object {
private var nextBit = 0
private val iota: Flags
get() = Flags(1u shl nextBit++)

val None = Flags(0u)

val RequiresMouseMove = iota
val RequiresMouseDrag = iota

val All = Flags(iota.bits - 1u)

private val cache = ConcurrentHashMap<Class<*>, Flags>().apply {
put(Effect::class.java, Flags(0u))
put(UIComponent::class.java, Flags(0u))
put(Window::class.java, Flags(0u))
}
fun initialFor(cls: Class<*>): Flags = cache.getOrPut(cls) {
flagsBasedOnOverrides(cls) + initialFor(cls.superclass)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to add the flags from overrides onto the initial flag set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's literally what this does? I don't understand.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just being pedantic since if the left side is the first one and the right side is the other one, then it would imply that in this case, the override is the first one and the initial flags are the second.

}

private fun flagsBasedOnOverrides(cls: Class<*>): Flags = listOf(
if (cls.overridesMethod("mouseMove", Window::class.java)) RequiresMouseMove else None,
if (cls.overridesMethod("dragMouse", Int::class.java, Int::class.java, Int::class.java)) RequiresMouseDrag else None,
if (cls.overridesMethod("dragMouse", Float::class.java, Float::class.java, Int::class.java)) RequiresMouseDrag else None,
).reduce { acc, flags -> acc + flags }

private fun Class<*>.overridesMethod(name: String, vararg args: Class<*>) =
try { getDeclaredMethod(name, *args); true } catch (_: NoSuchMethodException) { false }
}
}

companion object {
// Default value for componentName used as marker for lazy init.
private val defaultComponentName = String()
Expand Down
23 changes: 20 additions & 3 deletions src/main/kotlin/gg/essential/elementa/components/Window.kt
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ class Window @JvmOverloads constructor(
// 2 and over. See [ElementaVersion.V2] for more info.
val (adjustedX, adjustedY) = pixelCoordinatesToPixelCenter(mouseX, mouseY)

prevDraggedMouseX = adjustedX.toFloat()
prevDraggedMouseY = adjustedY.toFloat()

doMouseClick(adjustedX, adjustedY, button)
}

Expand Down Expand Up @@ -270,6 +273,9 @@ class Window @JvmOverloads constructor(

super.mouseRelease()

prevDraggedMouseX = null
prevDraggedMouseY = null

currentMouseButton = -1
}

Expand All @@ -291,14 +297,25 @@ class Window @JvmOverloads constructor(
}
}

internal var prevDraggedMouseX: Float? = null
internal var prevDraggedMouseY: Float? = null

override fun animationFrame() {
if (currentMouseButton != -1) {
val (mouseX, mouseY) = getMousePosition()
if (version >= ElementaVersion.v2) {
dragMouse(mouseX, mouseY, currentMouseButton)
if (prevDraggedMouseX != mouseX && prevDraggedMouseY != mouseY) {
prevDraggedMouseX = mouseX
prevDraggedMouseY = mouseY
dragMouse(mouseX, mouseY, currentMouseButton)
}
} else {
@Suppress("DEPRECATION")
dragMouse(mouseX.toInt(), mouseY.toInt(), currentMouseButton)
if (prevDraggedMouseX != mouseX.toInt().toFloat() && prevDraggedMouseY != mouseY.toInt().toFloat()) {
prevDraggedMouseX = mouseX.toInt().toFloat()
prevDraggedMouseY = mouseY.toInt().toFloat()
@Suppress("DEPRECATION")
dragMouse(mouseX.toInt(), mouseY.toInt(), currentMouseButton)
}
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/main/kotlin/gg/essential/elementa/effects/Effect.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gg.essential.elementa.effects

import gg.essential.elementa.UIComponent
import gg.essential.elementa.UIComponent.Flags
import gg.essential.elementa.components.UpdateFunc
import gg.essential.universal.UMatrixStack

Expand All @@ -10,6 +11,20 @@ import gg.essential.universal.UMatrixStack
* This is where you can affect any drawing done.
*/
abstract class Effect {
internal var flags: Flags = Flags.initialFor(javaClass)
set(newValue) {
val oldValue = field
if (oldValue == newValue) return
field = newValue
updateFuncParent?.let { parent ->
if (oldValue in newValue) { // merely additions?
parent.effectFlags += newValue
} else {
parent.recomputeEffectFlags()
}
}
}

protected lateinit var boundComponent: UIComponent

fun bindComponent(component: UIComponent) {
Expand Down