-
Notifications
You must be signed in to change notification settings - Fork 10
feat(nui-libgdx): replace touch pointer removal logic with configurable stickiness #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,13 @@ public class LibGDXMouseDevice implements MouseDevice { | |
| // The maximum number of touches that LibGDX supports on Android is 20. | ||
| // See https://github.com/libgdx/libgdx/blob/5eac848925d6e1f24070f887cbfaf99bb8bc4a63/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidInput.java#L99 | ||
| private static final int MAX_POINTERS = 20; | ||
| // The default value has been calibrated by trial and error, mostly. You can set your own stickiness in the constructor. | ||
| private static final int DEFAULT_POINTER_STICKINESS = 4; | ||
| /** | ||
| * Defines how "sticky" a pointer is, meaning how many updates it should be retained for after the touch is released. | ||
| * This value can be fine-tuned based on the desired general responsiveness of the UI. | ||
| */ | ||
| private final int pointerStickiness; | ||
| /** | ||
| * Flags a pointer for "removal" when it is no longer present on the screen. | ||
| * | ||
|
|
@@ -43,11 +50,20 @@ public class LibGDXMouseDevice implements MouseDevice { | |
| * The removePointer variable is used to delay this removal by a single update, so that UI widgets have time | ||
| * to register the removal first (e.g. for button de-presses). | ||
| */ | ||
| private boolean[] removePointer; | ||
| private final int[] pointerCooldowns; | ||
|
|
||
| public LibGDXMouseDevice() { | ||
| this(DEFAULT_POINTER_STICKINESS); | ||
| } | ||
|
|
||
| /** | ||
| * @param pointerStickiness Defines how "sticky" a pointer is, | ||
| * meaning how many updates it should be retained for after the touch is released. | ||
| */ | ||
| public LibGDXMouseDevice(int pointerStickiness) { | ||
| NUIInputProcessor.init(); | ||
| removePointer = new boolean[MAX_POINTERS]; | ||
| this.pointerStickiness = pointerStickiness; | ||
| pointerCooldowns = new int[MAX_POINTERS]; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -72,15 +88,12 @@ public Vector2i getPosition(int pointer) { | |
|
|
||
| if (Gdx.app.getType() == Application.ApplicationType.Android) { | ||
| if (Gdx.input.isTouched(pointer)) { | ||
| removePointer[pointer] = false; | ||
| } else { | ||
| if (removePointer[pointer]) { | ||
| // Since touches are mapped to pointers on Android, reset the pointer when not currently touching. | ||
| // Set the pointer to an off-screen location, so it acts as if it were not present. | ||
| return new Vector2i(Integer.MAX_VALUE, Integer.MAX_VALUE); | ||
| } else { | ||
| removePointer[pointer] = true; | ||
| } | ||
| pointerCooldowns[pointer] = pointerStickiness; | ||
| } else if (pointerCooldowns[pointer] <= 0) { | ||
| pointerCooldowns[pointer] = 0; | ||
| // Since touches are mapped to pointers on Android, reset the pointer when not currently touching. | ||
| // Set the pointer to an off-screen location, so it acts as if it were not present. | ||
| return new Vector2i(Integer.MAX_VALUE, Integer.MAX_VALUE); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -106,6 +119,11 @@ public boolean isButtonDown(int button) { | |
|
|
||
| @Override | ||
| public void update() { | ||
| for (int pointer = 0; pointer < MAX_POINTERS; pointer++) { | ||
| if (pointerCooldowns[pointer] > 0) { | ||
| pointerCooldowns[pointer]--; | ||
| } | ||
| } | ||
|
Comment on lines
+122
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect pointer polling and device update call sites.
ast-grep run --lang java --pattern '$DEVICE.getPosition($_)' .
ast-grep run --lang java --pattern '$DEVICE.update()' .Repository: MovingBlocks/TeraNUI Length of output: 158 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Find target file"
fd -a 'LibGDXMouseDevice\.java$' . || true
echo "List Java mouse/device files"
fd -a 'Mouse.*\.java$|.*Mouse.*\.java$' . | sed -n '1,200p'
echo "Search for MouseDevice references"
rg -n "MouseDevice|getPosition\\(|\\.update\\(\\)" --glob '*.java' .Repository: MovingBlocks/TeraNUI Length of output: 8662 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Target file outline"
ast-grep outline nui-libgdx/src/main/java/org/terasology/nui/backends/libgdx/LibGDXMouseDevice.java --view expanded || true
echo "Target file relevant content"
cat -n nui-libgdx/src/main/java/org/terasology/nui/backends/libgdx/LibGDXMouseDevice.java | sed -n '1,180p'
echo "InputDevice contract"
cat -n nui-input/src/main/java/org/terasology/input/device/InputDevice.java | sed -n '1,220p' || true
echo "MouseDevice contract"
cat -n nui-input/src/main/java/org/terasology/input/device/MouseDevice.java | sed -n '1,220p' || true
echo "KeyboardDevice/Device update call sites"
rg -n "extends .*Update|implements .*Update|void update\\(|\\.update\\(\\)|getPointer|getPointers|pointerCooldowns|pointerStickiness|pointerCooldown" --glob '*.java' .Repository: MovingBlocks/TeraNUI Length of output: 14410 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Find update callers with mouse context"
rg -n -C 3 "\.update\(\)" --glob '*.java' . | rg -C 5 "mouse|Mouse|InputDevice|InputProcessor|NUIInputProcessor" || true
echo "Search NUIInputProcessor update and sampling"
fd -a 'NUIInputProcessor\.java$|.*InputProcessor.*\.java$' .
rg -n "NUIInputProcessor|MouseInputQueue|mouse|Pointer|update\\(|getPosition" --glob '*.java' nui nui-input nui-libgdx | sed -n '1,260p'
echo "NullMouseDevice"
cat -n nui-input/src/main/java/org/terasology/input/device/nulldevices/NullMouseDevice.javaRepository: MovingBlocks/TeraNUI Length of output: 37804 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "CanvasImpl pointer polling/update relevant content"
cat -n nui/src/main/java/org/terasology/nui/canvas/CanvasImpl.java | sed -n '1,260p'
echo "NUIInputProcessor relevant content"
cat -n nui-libgdx/src/main/java/org/terasology/nui/backends/libgdx/NUIInputProcessor.java | sed -n '1,200p'
echo "Programmatic check: update decrements before position sampling is required by contract? (source search)"
python3 - <<'PY'
from pathlib import Path
import re
target = Path("nui-libgdx/src/main/java/org/terasology/nui/backends/libgdx/LibGDXMouseDevice.java").read_text()
iface = Path("nui-input/src/main/java/org/terasology/input/device/MouseDevice.java").read_text()
canvas = Path("nui/src/main/java/org/terasology/nui/canvas/CanvasImpl.java").read_text()
print("LibGDXMouseDevice decres in update:", bool(re.search(r'public void update\(\).*pointerCooldowns\[pointer\]--', target, re.S)))
print("getPosition(int) reloads only on touched:", bool(re.search(r'if \(Gdx\.input\.isTouched\(pointer\)\).*pointerCooldowns\[pointer\] = pointerStickiness', target, re.S)))
print("getPosition(int) returns off-screen after release unless cooldown:", bool(re.search(r'} else if \(pointerCooldowns\[pointer\] <= 0\).*return new Vector2i\(Integer\.MAX_VALUE, Integer\.MAX_VALUE\)', target, re.S)))
print("CanvasImpl update starts with polling:", bool(re.search(r'for \(int ptr = 0; ptr < getMaxPointers\(\); ptr\+\+\) \{[\s\S]*getPosition\(ptr\);', canvas)))
print("MouseDevice contract requires poll-before-update:", bool(re.search(r'Decrement cooldown|sampling order|before calling|always poll', iface)))
# Behavioral probe of local state machine with sampled vs skipped pointer.
MAX_POINTERS = 20
stickiness = 3
cooldowns = [0]*MAX_POINTERS
def getPosition(p):
pos = (10,10)
if True:
if True:
cooldowns[p] = stickiness
elif cooldowns[p] <= 0:
cooldowns[p] = 0
return (2**31-1, 2**31-1)
return pos
def update():
global cooldowns
cooldowns = [c-1 if c > 0 else 0 for c in cooldowns]
cooldowns[0] = 3
update() # skipped until release
print("skipped until release position after update:", getPosition(0))
cooldowns = [0]*MAX_POINTERS
cooldowns[0] = 3
getPosition(0) # sampled touches reset
update() # after sampling release
print("sampled once after update:", getPosition(0))
PYRepository: MovingBlocks/TeraNUI Length of output: 18746 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Find MouseDevice implementations/update calls with exact Java syntax"
python3 - <<'PY'
from pathlib import Path
import re
paths=[]
for path in Path('.').rglob('*.java'):
text=path.read_text(errors='ignore')
if re.search(r'\b(mouse|mousedevice|libgdxmousedevice)\b|getPosition\s*\(|\bupdate\s*\(\s*\)', text, re.I):
paths.append(path)
for path in paths:
text=path.read_text(errors='ignore')
lines=text.splitlines()
print(f"\n{'='*60}\n{path}")
for i,line in enumerate(lines,1):
if any(s in line for s in ['getPosition(', '.update()', '.getMaxPointers()', 'new LibGDXMouseDevice', 'processMouse', 'canvas.update', 'mouse.update']):
lo=max(1,i-4); hi=min(len(lines),i+4)
for n in range(lo,hi+1):
print(f"{n:5d}: {lines[n-1]}")
PY
echo "Search exact CursorAttachment update/call path"
cat -n nui/src/main/java/org/terasology/nui/widgets/CursorAttachment.java | sed -n '45,110p'
rg -n "update\(" nui/src/main/java/org/terasology/nui/widgets/CursorAttachment.javaRepository: MovingBlocks/TeraNUI Length of output: 24233 Guard pointer stickiness from
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject negative
pointerStickinessvalues.The public constructor accepts a negative update count. For
-1, Line 91 stores a negative cooldown, and Line 92 treats it as expired. Invalid input therefore silently becomes zero stickiness. ValidatepointerStickiness >= 0; keep zero valid for immediate removal.Proposed fix
public LibGDXMouseDevice(int pointerStickiness) { + if (pointerStickiness < 0) { + throw new IllegalArgumentException("pointerStickiness must be non-negative"); + } NUIInputProcessor.init();📝 Committable suggestion
🤖 Prompt for AI Agents