修鼠标滚轮(第3版):面板按钮动作后把编辑器的滚动焦点抢到列表上
- 诊断证据:给列表 ScrollPane 挂 EventListener 代理后,滚轮事件收到 0 条 → 滚轮被视口的 scroll focus 拿走; - grabScrollFocus():只在 **Stage 自己声明**的方法里找(addActor 声明在 Group,不会误调); setter=『1 个参数是 Actor、返回 void』,getter=『无参返回 Actor』;调 setter 后看哪个 getter 变成面板, 找到就写日志『滚动焦点已抢到面板:setter=.. getter=..』,找不到写『没找到 setScrollFocus』。 - 触发时机:每次点击面板按钮/字段行之后(路由器里统一调用)。
This commit is contained in:
1 parent
995ec585f4
commit
4b05804479
1 file changed
+129
@@ -49,6 +49,7 @@ public final class ObjectPanel {
|
|||||||
java.util.List<Object[]> inspectFields = new ArrayList<Object[]>();
|
java.util.List<Object[]> inspectFields = new ArrayList<Object[]>();
|
||||||
List<Object> objects = new ArrayList<Object>();
|
List<Object> objects = new ArrayList<Object>();
|
||||||
Object target;
|
Object target;
|
||||||
|
boolean scrollFocusLogged;
|
||||||
Class<?> tableClass;
|
Class<?> tableClass;
|
||||||
Class<?> scrollClass;
|
Class<?> scrollClass;
|
||||||
Class<?> actorClass;
|
Class<?> actorClass;
|
||||||
@@ -61,6 +62,7 @@ public final class ObjectPanel {
|
|||||||
*/
|
*/
|
||||||
public static Object build(ClassLoader loader, Object skin) throws Exception {
|
public static Object build(ClassLoader loader, Object skin) throws Exception {
|
||||||
final State state = new State();
|
final State state = new State();
|
||||||
|
LAST_STATE = state;
|
||||||
state.loader = loader;
|
state.loader = loader;
|
||||||
state.skin = skin;
|
state.skin = skin;
|
||||||
|
|
||||||
@@ -85,6 +87,7 @@ public final class ObjectPanel {
|
|||||||
Object scroll = scrollClass.getConstructor(actorClass, skinClass).newInstance(state.listTable, skin);
|
Object scroll = scrollClass.getConstructor(actorClass, skinClass).newInstance(state.listTable, skin);
|
||||||
state.listScroll = scroll;
|
state.listScroll = scroll;
|
||||||
makeTouchable(scroll);
|
makeTouchable(scroll);
|
||||||
|
attachEventProbe(scroll, "listScroll");
|
||||||
state.status = label(loader, skin, "Ready. Press Refresh to list scene objects.");
|
state.status = label(loader, skin, "Ready. Press Refresh to list scene objects.");
|
||||||
Object logScroll = null;
|
Object logScroll = null;
|
||||||
state.nameField = fieldClass.getConstructor(String.class, skinClass)
|
state.nameField = fieldClass.getConstructor(String.class, skinClass)
|
||||||
@@ -548,6 +551,128 @@ public final class ObjectPanel {
|
|||||||
* 不靠名字定位:`Actor.getTouchable()` 是"**无参 + 返回枚举**"的那个方法;枚举第 0 个常量
|
* 不靠名字定位:`Actor.getTouchable()` 是"**无参 + 返回枚举**"的那个方法;枚举第 0 个常量
|
||||||
* 就是 `Touchable.enabled`(libGDX 的声明顺序 enabled/disabled/childrenOnly)。
|
* 就是 `Touchable.enabled`(libGDX 的声明顺序 enabled/disabled/childrenOnly)。
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 只读诊断:给某个 Actor 挂一个事件监听(`dn` = EventListener 接口的 **动态代理**),
|
||||||
|
* 把收到的每个事件打出来。用来确认"鼠标滚轮到底有没有送到面板"。
|
||||||
|
*
|
||||||
|
* 为什么要代理:载荷里的监听器都是匿名内部类,反射 new 不出来;
|
||||||
|
* 但 `dn` 是接口,用 `java.lang.reflect.Proxy` 就能造一个。
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 把编辑器的**滚动焦点**抢到面板的列表上(鼠标滚轮才会滚列表)。
|
||||||
|
*
|
||||||
|
* 识别方式(不猜名字、也不会误调 addActor):
|
||||||
|
* - 只在 **Stage 自己声明的**方法里找(`uk`/`dt`/`lpt6` 这些层),
|
||||||
|
* `addActor` 声明在 Group 上、不在这一层,所以不会被误选;
|
||||||
|
* - 候选 setter = 「1 个参数是 Actor 类型、返回 void」;候选 getter = 「无参、返回 Actor 类型」;
|
||||||
|
* - 调 setter 后看哪个 getter 变成了我们的面板 —— 那就是 setScrollFocus 那一对,日志会写出来。
|
||||||
|
*/
|
||||||
|
static void grabScrollFocus(final State state) {
|
||||||
|
try {
|
||||||
|
Object stage = SceneObjects.global(state.loader, "uk");
|
||||||
|
if (stage == null) stage = SceneObjects.global(state.loader, "dt");
|
||||||
|
if (stage == null || state.listScroll == null) return;
|
||||||
|
Class<?> actorClass = state.actorClass;
|
||||||
|
java.util.List<Method> getters = new ArrayList<Method>();
|
||||||
|
java.util.List<Method> setters = new ArrayList<Method>();
|
||||||
|
for (Class<?> type = stage.getClass(); type != null && type != Object.class;
|
||||||
|
type = type.getSuperclass()) {
|
||||||
|
if (type.getName().equals(Obf.GROUP)) break; // 到 Group 就停(addActor 在这一层)
|
||||||
|
for (Method method : type.getDeclaredMethods()) {
|
||||||
|
Class<?>[] types = method.getParameterTypes();
|
||||||
|
if (types.length == 0 && actorClass.isAssignableFrom(method.getReturnType())) {
|
||||||
|
getters.add(method);
|
||||||
|
} else if (types.length == 1 && types[0].isAssignableFrom(actorClass)
|
||||||
|
&& method.getReturnType() == void.class) {
|
||||||
|
setters.add(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type.getName().equals("dt")) continue;
|
||||||
|
}
|
||||||
|
for (Method setter : setters) {
|
||||||
|
try {
|
||||||
|
setter.setAccessible(true);
|
||||||
|
setter.invoke(stage, state.listScroll);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (Method getter : getters) {
|
||||||
|
try {
|
||||||
|
getter.setAccessible(true);
|
||||||
|
if (getter.invoke(stage) == state.listScroll) {
|
||||||
|
if (state.scrollFocusLogged) return;
|
||||||
|
state.scrollFocusLogged = true;
|
||||||
|
System.err.println("[panel] 滚动焦点已抢到面板:setter=" + setter.getName()
|
||||||
|
+ " getter=" + getter.getName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// 下一个
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!state.scrollFocusLogged) {
|
||||||
|
state.scrollFocusLogged = true;
|
||||||
|
System.err.println("[panel] 没找到 setScrollFocus(候选 setter " + setters.size()
|
||||||
|
+ " 个 / getter " + getters.size() + " 个)");
|
||||||
|
}
|
||||||
|
} catch (Throwable error) {
|
||||||
|
System.err.println("[panel] 抢滚动焦点失败: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void attachEventProbe(final Object actor, final String tag) {
|
||||||
|
try {
|
||||||
|
Class<?> listenerClass = Class.forName("dn", true, actor.getClass().getClassLoader());
|
||||||
|
if (!listenerClass.isInterface()) {
|
||||||
|
System.err.println("[probe] dn 不是接口,跳过");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object listener = java.lang.reflect.Proxy.newProxyInstance(
|
||||||
|
listenerClass.getClassLoader(), new Class<?>[] {listenerClass},
|
||||||
|
new java.lang.reflect.InvocationHandler() {
|
||||||
|
@Override
|
||||||
|
public Object invoke(Object proxy, Method method, Object[] args) {
|
||||||
|
if (method.getName().equals("handle") && args != null && args.length == 1) {
|
||||||
|
Object event = args[0];
|
||||||
|
String name = event == null ? "null" : event.getClass().getName();
|
||||||
|
String type = event == null ? "" : String.valueOf(readField(event, "D"));
|
||||||
|
System.err.println("[probe] " + tag + " event=" + name + " typeField=" + type);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (Method method : actor.getClass().getMethods()) {
|
||||||
|
Class<?>[] types = method.getParameterTypes();
|
||||||
|
if (types.length != 1 || types[0] != listenerClass) continue;
|
||||||
|
method.setAccessible(true);
|
||||||
|
Object result = method.invoke(actor, listener);
|
||||||
|
System.err.println("[probe] 已给 " + tag + " 挂事件监听: " + method.getName() + " -> " + result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
System.err.println("[probe] 找不到 addListener");
|
||||||
|
} catch (Throwable error) {
|
||||||
|
System.err.println("[probe] 挂事件监听失败: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读一个字段(诊断用,读不到返回 "?")。 */
|
||||||
|
static Object readField(Object target, String name) {
|
||||||
|
for (Class<?> type = target.getClass(); type != null && type != Object.class;
|
||||||
|
type = type.getSuperclass()) {
|
||||||
|
for (java.lang.reflect.Field field : type.getDeclaredFields()) {
|
||||||
|
if (!field.getName().equals(name)) continue;
|
||||||
|
try {
|
||||||
|
field.setAccessible(true);
|
||||||
|
return field.get(target);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
static void makeTouchable(Object actor) {
|
static void makeTouchable(Object actor) {
|
||||||
try {
|
try {
|
||||||
Class<?> enumType = null;
|
Class<?> enumType = null;
|
||||||
@@ -774,6 +899,8 @@ public final class ObjectPanel {
|
|||||||
private static volatile boolean routerStarted;
|
private static volatile boolean routerStarted;
|
||||||
|
|
||||||
/** 验证用:记住面板上的按钮(标签 → 按钮),探针可以按标签"点"它们。 */
|
/** 验证用:记住面板上的按钮(标签 → 按钮),探针可以按标签"点"它们。 */
|
||||||
|
private static volatile ObjectPanel.State LAST_STATE;
|
||||||
|
|
||||||
private static final java.util.Map<String, Object> BUTTONS =
|
private static final java.util.Map<String, Object> BUTTONS =
|
||||||
new java.util.LinkedHashMap<String, Object>();
|
new java.util.LinkedHashMap<String, Object>();
|
||||||
|
|
||||||
@@ -883,6 +1010,8 @@ public final class ObjectPanel {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
((Runnable) route[1]).run();
|
((Runnable) route[1]).run();
|
||||||
|
Object st = LAST_STATE;
|
||||||
|
if (st instanceof State) grabScrollFocus((State) st);
|
||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
System.err.println("[panel] 动作失败: " + error);
|
System.err.println("[panel] 动作失败: " + error);
|
||||||
error.printStackTrace(System.err);
|
error.printStackTrace(System.err);
|
||||||
|
|||||||
Reference in new issue
Block a user