Skip to content

Commit dc23648

Browse files
committed
UIComponent & ScrollComponent: Code review fixes & improved scrolling in ScrollComponent
Linear: EM-3575
1 parent 5d6aeda commit dc23648

7 files changed

Lines changed: 90 additions & 41 deletions

File tree

api/Elementa.api

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ public abstract class gg/essential/elementa/WindowScreen : gg/essential/universa
274274
public fun onKeyPressed (ICLgg/essential/universal/UKeyboard$Modifiers;)V
275275
public fun onMouseClicked (DDI)V
276276
public fun onMouseReleased (DDI)V
277+
public fun onMouseScrolled (D)V
277278
public fun onMouseScrolled (DDDD)V
278279
public fun onScreenClose ()V
279280
public final fun stopAnimating (Lkotlin/reflect/KMutableProperty0;)V
@@ -2750,6 +2751,7 @@ public abstract class gg/essential/elementa/events/UIEvent {
27502751
}
27512752

27522753
public final class gg/essential/elementa/events/UIScrollEvent : gg/essential/elementa/events/UIEvent {
2754+
public fun <init> (DDLgg/essential/elementa/UIComponent;Lgg/essential/elementa/UIComponent;)V
27532755
public fun <init> (DLgg/essential/elementa/UIComponent;Lgg/essential/elementa/UIComponent;)V
27542756
public fun <init> (DLgg/essential/elementa/UIComponent;Lgg/essential/elementa/UIComponent;D)V
27552757
public final fun component1 ()D
@@ -2763,8 +2765,8 @@ public final class gg/essential/elementa/events/UIScrollEvent : gg/essential/ele
27632765
public fun equals (Ljava/lang/Object;)Z
27642766
public final fun getCurrentTarget ()Lgg/essential/elementa/UIComponent;
27652767
public final fun getDelta ()D
2766-
public final fun getDeltaHorizontal ()D
2767-
public final fun getDeltaVertical ()D
2768+
public final fun getScrollX ()D
2769+
public final fun getScrollY ()D
27682770
public final fun getTarget ()Lgg/essential/elementa/UIComponent;
27692771
public fun hashCode ()I
27702772
public fun toString ()Ljava/lang/String;

src/main/kotlin/gg/essential/elementa/ElementaVersion.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,11 @@ enum class ElementaVersion {
196196
* Improvements for vertical scrolling and support for horizontal scrolling.
197197
*
198198
* Adds new mouseScroll() function to UIComponent that also supports horizontal scrolling.
199-
* The old function is no longer called by WindowScreen.
200-
* Vertical deltas passed to the new function are now passed along unchanged.
201-
* Previous they were coerced between -1.0 and 1.0 in WindowScreen.
199+
* The old function is no longer supported and will no longer be called.
200+
* Previously, deltas were always coerced between -1.0 and 1.0 in WindowScreen.
201+
* This was wrong, and is no longer the case for the new function.
202202
* UIScrollEvent now includes also the horizontal delta.
203+
* ScrollComponent now also properly supports new horizontal scrolling.
203204
*
204205
* See [UniversalCraft#128](https://github.com/SparkUniverse/UniversalCraft/pull/128) for the underlying changes.
205206
*/

src/main/kotlin/gg/essential/elementa/UIComponent.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -751,18 +751,18 @@ abstract class UIComponent : Observable(), ReferenceHolder {
751751
* Use this in the proper mouse scroll event to cascade all component's mouse scroll events.
752752
* Most common use is on the [Window] object.
753753
*/
754-
open fun mouseScroll(deltaHorizontal: Double, deltaVertical: Double) {
755-
if (deltaHorizontal == 0.0 && deltaVertical == 0.0) return
754+
open fun mouseScroll(scrollX: Double, scrollY: Double) {
755+
if (scrollX == 0.0 && scrollY == 0.0) return
756756

757757
for (i in children.lastIndex downTo 0) {
758758
val child = children[i]
759759

760760
if (child.isHovered()) {
761-
return child.mouseScroll(deltaHorizontal, deltaVertical)
761+
return child.mouseScroll(scrollX, scrollY)
762762
}
763763
}
764764

765-
fireScrollEvent(UIScrollEvent(deltaVertical, this, this, deltaHorizontal))
765+
fireScrollEvent(UIScrollEvent(scrollX, scrollY, this, this))
766766
}
767767

768768
open fun onWindowResize() {

src/main/kotlin/gg/essential/elementa/WindowScreen.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,25 @@ abstract class WindowScreen @JvmOverloads constructor(
8686
window.mouseRelease()
8787
}
8888

89+
@Suppress("DEPRECATION")
90+
@Deprecated(
91+
"Provided `delta` values have different units depending on Minecraft versions. See ElementaVersion.V11 for details.",
92+
replaceWith = ReplaceWith("onMouseScrolled(mouseX, mouseY, deltaHorizontal, deltaVertical)")
93+
)
94+
override fun onMouseScrolled(delta: Double) {
95+
super.onMouseScrolled(delta)
96+
97+
if (version < ElementaVersion.v11) {
98+
// We also need to pass along scrolling
99+
window.mouseScroll(delta.coerceIn(-1.0, 1.0))
100+
}
101+
}
102+
89103
override fun onMouseScrolled(mouseX: Double, mouseY: Double, deltaHorizontal: Double, deltaVertical: Double) {
90104
super.onMouseScrolled(mouseX, mouseY, deltaHorizontal, deltaVertical)
91105

92106
if (version >= ElementaVersion.v11) {
93107
window.mouseScroll(deltaHorizontal, deltaVertical)
94-
} else {
95-
@Suppress("DEPRECATION")
96-
window.mouseScroll(deltaVertical.coerceIn(-1.0, 1.0))
97108
}
98109
}
99110

src/main/kotlin/gg/essential/elementa/components/ScrollComponent.kt

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package gg.essential.elementa.components
22

33
import gg.essential.elementa.ElementaVersion
44
import gg.essential.elementa.UIComponent
5-
import gg.essential.elementa.components.UpdateFunc
65
import gg.essential.elementa.constraints.*
76
import gg.essential.elementa.constraints.animation.Animations
87
import gg.essential.elementa.constraints.resolution.ConstraintVisitor
@@ -143,7 +142,29 @@ class ScrollComponent constructor(
143142

144143

145144
private val mouseScrollLambda: UIComponent.(UIScrollEvent) -> Unit = {
146-
if (Window.of(this).version >= ElementaVersion.v5) {
145+
if (Window.of(this).version >= ElementaVersion.v11) {
146+
// For easier understanding we reframe the provided scroll in terms of primary and secondary directions
147+
// We used to only get vertical scrolls, so that remains as the primary direction
148+
val scrollPrimary = it.scrollY.toFloat()
149+
val scrollSecondary = it.scrollX.toFloat()
150+
151+
// We map the primary and secondary scroll values based on the scroll direction
152+
// `Vertical` and `Horizontal` directions disable the other direction, so we use 0f
153+
val (providedX, providedY) = when (scrollDirection) {
154+
Direction.Vertical -> 0f to scrollPrimary
155+
Direction.Horizontal -> scrollPrimary to 0f
156+
Direction.PreferVertical -> scrollSecondary to scrollPrimary
157+
Direction.PreferHorizontal -> scrollPrimary to scrollSecondary
158+
}
159+
160+
// We swap directions if shift is pressed
161+
val (actualX, actualY) = if (UKeyboard.isShiftKeyDown()) providedY to providedX else providedX to providedY
162+
163+
// Finally, process the scroll with computed values
164+
if (onScroll(actualX, actualY) || !passthroughScroll) {
165+
it.stopPropagation()
166+
}
167+
} else if (Window.of(this).version >= ElementaVersion.v5) {
147168
// new behavior
148169
val scrollDirection = if (!UKeyboard.isShiftKeyDown()) primaryScrollDirection else secondaryScrollDirection
149170
if (scrollDirection != null) {
@@ -207,8 +228,7 @@ class ScrollComponent constructor(
207228

208229
if (needsUpdate) {
209230
needsUpdate = false
210-
val horizontalRange = calculateOffsetRange(isHorizontal = true)
211-
val verticalRange = calculateOffsetRange(isHorizontal = false)
231+
val (horizontalRange, verticalRange) = calculateOffsetRanges()
212232

213233
// Recalculate our scroll box and move the content inside if needed.
214234
actualHolder.animate {
@@ -317,8 +337,7 @@ class ScrollComponent constructor(
317337
verticalOffset: Float = this.verticalOffset,
318338
smoothScroll: Boolean = true
319339
) {
320-
val horizontalRange = calculateOffsetRange(isHorizontal = true)
321-
val verticalRange = calculateOffsetRange(isHorizontal = false)
340+
val (horizontalRange, verticalRange) = calculateOffsetRanges()
322341
this.horizontalOffset =
323342
if (horizontalRange.isEmpty()) innerPadding else horizontalOffset.coerceIn(horizontalRange)
324343
this.verticalOffset = if (verticalRange.isEmpty()) {
@@ -425,13 +444,26 @@ class ScrollComponent constructor(
425444
* @return whether the offset changed
426445
*/
427446
private fun onScroll(delta: Float, isHorizontal: Boolean): Boolean {
447+
return if (isHorizontal) onScroll(delta, 0f) else onScroll(0f, delta)
448+
}
449+
450+
/**
451+
* @return whether either offset changed
452+
*/
453+
private fun onScroll(scrollX: Float, scrollY: Float): Boolean {
428454
var changed = false
429-
val offset = if (isHorizontal) ::horizontalOffset else ::verticalOffset
430-
val range = calculateOffsetRange(isHorizontal)
431-
val newOffset = if(range.isEmpty()) innerPadding else (offset.get() + delta * pixelsPerScroll * currentScrollAcceleration).coerceIn(range)
432-
if (newOffset != offset.get()) {
455+
val offsetX = ::horizontalOffset
456+
val offsetY = ::verticalOffset
457+
val (rangeX, rangeY) = calculateOffsetRanges()
458+
val newOffsetX = if (rangeX.isEmpty()) innerPadding else (offsetX.get() + scrollX * pixelsPerScroll * currentScrollAcceleration).coerceIn(rangeX)
459+
if (newOffsetX != offsetX.get()) {
433460
changed = true
434-
offset.set(newOffset)
461+
offsetX.set(newOffsetX)
462+
}
463+
val newOffsetY = if (rangeY.isEmpty()) innerPadding else (offsetY.get() + scrollY * pixelsPerScroll * currentScrollAcceleration).coerceIn(rangeY)
464+
if (newOffsetY != offsetY.get()) {
465+
changed = true
466+
offsetY.set(newOffsetY)
435467
}
436468

437469
currentScrollAcceleration =
@@ -511,16 +543,16 @@ class ScrollComponent constructor(
511543
}
512544
}
513545

514-
private fun calculateOffsetRange(isHorizontal: Boolean): ClosedFloatingPointRange<Float> {
515-
return if (isHorizontal) {
516-
val actualWidth = calculateActualWidth()
517-
val maxNegative = this.getWidth() - actualWidth - innerPadding
518-
if (horizontalScrollOpposite) (-innerPadding)..-maxNegative else maxNegative..(innerPadding)
519-
} else {
520-
val actualHeight = calculateActualHeight()
521-
val maxNegative = this.getHeight() - actualHeight - innerPadding
522-
if (verticalScrollOpposite) (-innerPadding)..-maxNegative else maxNegative..(innerPadding)
523-
}
546+
private fun calculateOffsetRanges(): Pair<ClosedFloatingPointRange<Float>, ClosedFloatingPointRange<Float>> {
547+
val actualWidth = calculateActualWidth()
548+
val maxNegativeWidth = this.getWidth() - actualWidth - innerPadding
549+
val rangeX = if (horizontalScrollOpposite) (-innerPadding)..-maxNegativeWidth else maxNegativeWidth..(innerPadding)
550+
551+
val actualHeight = calculateActualHeight()
552+
val maxNegativeHeight = this.getHeight() - actualHeight - innerPadding
553+
val rangeY = if (verticalScrollOpposite) (-innerPadding)..-maxNegativeHeight else maxNegativeHeight..(innerPadding)
554+
555+
return rangeX to rangeY
524556
}
525557

526558
private fun onClick(mouseX: Float, mouseY: Float, mouseButton: Int) {

src/main/kotlin/gg/essential/elementa/components/Window.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ class Window @JvmOverloads constructor(
244244
super.mouseScroll(delta)
245245
}
246246

247-
override fun mouseScroll(deltaHorizontal: Double, deltaVertical: Double) {
247+
override fun mouseScroll(scrollX: Double, scrollY: Double) {
248248
if (hasErrored && version >= ElementaVersion.v7) {
249249
return
250250
}
@@ -254,12 +254,12 @@ class Window @JvmOverloads constructor(
254254
val (mouseX, mouseY) = getMousePosition()
255255
for (floatingComponent in allFloatingComponentsInReverseOrder()) {
256256
if (floatingComponent.isPointInside(mouseX, mouseY)) {
257-
floatingComponent.mouseScroll(deltaHorizontal, deltaVertical)
257+
floatingComponent.mouseScroll(scrollX, scrollY)
258258
return
259259
}
260260
}
261261

262-
super.mouseScroll(deltaHorizontal, deltaVertical)
262+
super.mouseScroll(scrollX, scrollY)
263263
}
264264

265265
override fun mouseClick(mouseX: Double, mouseY: Double, button: Int) {

src/main/kotlin/gg/essential/elementa/events/UIMouseEvents.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,25 @@ data class UIClickEvent(
1515
}
1616

1717
data class UIScrollEvent(
18-
val deltaVertical: Double,
18+
val scrollY: Double,
1919
val target: UIComponent,
2020
val currentTarget: UIComponent,
21-
val deltaHorizontal: Double,
21+
val scrollX: Double, // only on Minecraft 1.20.2+ and ElementaVersion.V11+
2222
) : UIEvent() {
23+
constructor(scrollX: Double, scrollY: Double, target: UIComponent, currentTarget: UIComponent)
24+
: this(scrollY, target, currentTarget, scrollX)
25+
2326
// Added to ensure backwards binary compatibility
2427
constructor(delta: Double, target: UIComponent, currentTarget: UIComponent) : this(delta, target, currentTarget, 0.0)
2528

2629
// Added to ensure backwards binary compatibility
2730
fun copy(
28-
deltaVertical: Double = this.deltaVertical,
31+
delta: Double = this.scrollY,
2932
target: UIComponent = this.target,
3033
currentTarget: UIComponent = this.currentTarget,
31-
) = copy(deltaVertical = deltaVertical, target = target, currentTarget = currentTarget, deltaHorizontal = deltaHorizontal)
34+
) = copy(scrollY = delta, target = target, currentTarget = currentTarget, scrollX = scrollX)
3235

3336
// Added to ensure backwards binary compatibility
3437
val delta: Double
35-
get() = deltaVertical
38+
get() = scrollY
3639
}

0 commit comments

Comments
 (0)