diff --git a/tools/deobf/anchors.tsv b/tools/deobf/anchors.tsv index 4a30262..8d3e1a5 100644 --- a/tools/deobf/anchors.tsv +++ b/tools/deobf/anchors.tsv @@ -87,3 +87,9 @@ dR S (F)LdR; minWidth dR C (F)LdR; minHeight dR Z (F)LdR; prefWidth dR V (F)LdR; prefHeight +# ---- Actor 的 Touchable(点击/进入命中相关;用于让 ScrollPane 收到鼠标进入→拿到 scroll focus)---- +# 定位方式(不靠名字):`Actor.getTouchable()` = **无参且返回枚举**的那个方法;枚举类 = dv; +# libGDX Touchable 声明顺序 enabled/disabled/childrenOnly → 第 0 个常量就是 enabled。 +# 现象佐证:ScrollPane 默认 childrenOnly 时自己不是命中目标,收不到 enter → 滚轮事件不会送到它, +# 但程序化 scrollY() 是生效的(实测 before=0 after=200)。 +dl L (Ldv;)V setTouchable diff --git a/tools/deobf/names.tsv b/tools/deobf/names.tsv index dd6491d..5d02f2b 100644 --- a/tools/deobf/names.tsv +++ b/tools/deobf/names.tsv @@ -143,6 +143,7 @@ dl I ()Z isTouchable dl L ()V clear dl L ()Z hasParent dl L (Ldn;)Z addCaptureListener +dl L (Ldv;)V setTouchable dl S (FF)V setOrigin dl S (Lde;)Lde; localToStageCoordinates dl V ()V toBack diff --git a/viewpanel/src/com/spineview/ObjectPanel.java b/viewpanel/src/com/spineview/ObjectPanel.java index b9fcd20..0156897 100644 --- a/viewpanel/src/com/spineview/ObjectPanel.java +++ b/viewpanel/src/com/spineview/ObjectPanel.java @@ -84,6 +84,7 @@ public final class ObjectPanel { call(state.listTable, Obf.TABLE_LEFT, new Class[0], tableClass); Object scroll = scrollClass.getConstructor(actorClass, skinClass).newInstance(state.listTable, skin); state.listScroll = scroll; + makeTouchable(scroll); state.status = label(loader, skin, "Ready. Press Refresh to list scene objects."); Object logScroll = null; state.nameField = fieldClass.getConstructor(String.class, skinClass) @@ -537,6 +538,64 @@ public final class ObjectPanel { state.logBlockStarted = false; } + /** + * 把 ScrollPane 的 touchable 设成 `enabled`(它默认是 childrenOnly)。 + * + * 为什么:ScrollPane 是靠自己的 InputListener 在鼠标"进入"时调 `Stage.setScrollFocus(this)` + * 来接收滚轮的;childrenOnly 时它自己不是命中目标,就永远收不到 enter → 滚轮事件不会送到它, + * 表现就是"面板能程序化滚动,但鼠标滚轮没用"。 + * + * 不靠名字定位:`Actor.getTouchable()` 是"**无参 + 返回枚举**"的那个方法;枚举第 0 个常量 + * 就是 `Touchable.enabled`(libGDX 的声明顺序 enabled/disabled/childrenOnly)。 + */ + static void makeTouchable(Object actor) { + try { + Class enumType = null; + for (Method method : actor.getClass().getMethods()) { + if (method.getParameterTypes().length != 0) continue; + Class returns = method.getReturnType(); + if (!returns.isEnum()) continue; + enumType = returns; + break; + } + if (enumType == null) { + System.err.println("[panel] 找不到 Touchable 枚举,跳过"); + return; + } + Object[] constants = enumType.getEnumConstants(); + if (constants == null || constants.length == 0) return; + Object enabled = constants[0]; + for (Method method : actor.getClass().getMethods()) { + Class[] types = method.getParameterTypes(); + if (types.length != 1 || types[0] != enumType) continue; + if (method.getReturnType() != void.class) continue; + method.setAccessible(true); + method.invoke(actor, enabled); + System.err.println("[panel] ScrollPane.touchable = " + enabled + + "(枚举 " + enumType.getName() + " 第 0 个常量)"); + return; + } + System.err.println("[panel] 找不到 setTouchable"); + } catch (Throwable error) { + System.err.println("[panel] 设置 touchable 失败: " + error); + } + } + + private static float readFloat(Object target, String name) { + try { + for (Method method : target.getClass().getMethods()) { + if (!method.getName().equals(name)) continue; + if (method.getParameterTypes().length != 0) continue; + if (method.getReturnType() != float.class) continue; + method.setAccessible(true); + return ((Float) method.invoke(target)).floatValue(); + } + } catch (Throwable ignored) { + // 读不到就返回 -1 + } + return -1f; + } + private static void invalidateList(final State state) { try { call(state.listTable, "b_", new Class[0], void.class); @@ -563,6 +622,13 @@ public final class ObjectPanel { // 再打开"强制纵向滚动":setForceScroll 在载荷里叫 `I`((ZZ)V,指纹确认 ~2/3) call(state.listScroll, "I", new Class[] {boolean.class, boolean.class}, void.class, Boolean.FALSE, Boolean.TRUE); + // 诊断(只读+可回滚):读一下当前滚动位置,再程序化滚 200,再读一次。 + // before/after 都写日志:如果 after 还是 0 → 说明 ScrollPane 认为"内容装得下"(高度没测到); + // 如果 after 变成 200 → 说明面板能滚,是滚轮事件没送到面板。 + float before = readFloat(state.listScroll, "invoke"); + call(state.listScroll, "Class", new Class[] {float.class}, void.class, Float.valueOf(200f)); + float after = readFloat(state.listScroll, "invoke"); + System.err.println("[panel] scroll diag: before=" + before + " after=" + after); } catch (Throwable error) { System.err.println("[panel] 恢复滚动失败: " + error); }