Inspector 变成可测数据的入口:字段行可点(读出当前值到输入框) + Set Value 按钮直接改模型字段(带撤销+重建树节点) + 跟随编辑器选择自动重新 Inspect

This commit is contained in:
tianmo committed 2026-10-16 16:08:05 +08:00
1 parent 0ec33d90ae
commit 5e7aed4a51
1 file changed
+168 -3
+168 -3
View File
@@ -43,6 +43,10 @@ public final class ObjectPanel {
final java.util.Deque<String> log = new java.util.ArrayDeque<String>();
boolean rendering;
boolean logBlockStarted;
Object inspected; // 当前 Inspect 的对象
java.lang.reflect.Field fieldTarget; // 被点选的字段(可写值)
Object fieldOwner;
java.util.List<Object[]> inspectFields = new ArrayList<Object[]>();
List<Object> objects = new ArrayList<Object>();
Object target;
}
@@ -95,9 +99,10 @@ public final class ObjectPanel {
Object delete = button(loader, skin, "Delete");
Object rename = button(loader, skin, "Rename");
Object inspect = button(loader, skin, "Inspect");
Object setValue = button(loader, skin, "Set Value");
// 按钮直接放进根表格:两行按钮 + 一行滚动列表(与阶段四验证过的结构一致)
Object[] top = {refresh, newBone, newSlot, newBBox, newPath, delete, rename, inspect};
Object[] top = {refresh, newBone, newSlot, newBBox, newPath, delete, rename, inspect, setValue};
for (int i = 0; i < top.length; i++) {
Object cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, top[i]);
layoutCell(cell, Obf.CELL_LEFT);
@@ -142,6 +147,12 @@ public final class ObjectPanel {
create(state, "Path");
}
});
wire(loader, setValue, new Runnable() {
@Override
public void run() {
applyFieldValue(state);
}
});
wire(loader, inspect, new Runnable() {
@Override
public void run() {
@@ -177,6 +188,7 @@ public final class ObjectPanel {
}
});
startSelectionWatcher(state);
System.err.println("[panel] 增删改查面板已建好");
refreshList(state);
return root;
@@ -343,9 +355,14 @@ public final class ObjectPanel {
return;
}
List<String> lines = new ArrayList<String>();
final java.util.Map<Integer, Object[]> fieldRows =
new java.util.HashMap<Integer, Object[]>();
lines.add("selected: " + SceneObjects.kind(object) + " \"" + SceneObjects.nameOf(object)
+ "\" (" + object.getClass().getName() + ")");
lines.add("--- fields (" + countFields(object) + ") ---");
state.inspected = object;
state.fieldTarget = null;
lines.add("--- fields (" + countFields(object) + ") --- (点一行字段可改值)");
final java.util.List<Object[]> fields = new ArrayList<Object[]>(); // {owner, field, valueText}
for (Class<?> type = object.getClass(); type != null && type != Object.class;
type = type.getSuperclass()) {
for (java.lang.reflect.Field field : type.getDeclaredFields()) {
@@ -360,10 +377,14 @@ public final class ObjectPanel {
} catch (Throwable error) {
value = "<" + error.getClass().getSimpleName() + ">";
}
final int rowIndex = lines.size();
lines.add(" " + shortType(field.getType().getName()) + " " + field.getName()
+ " = " + value + " @" + type.getName());
fieldRows.put(Integer.valueOf(rowIndex), new Object[] {object, field, value});
fields.add(new Object[] {object, field, value});
}
}
state.inspectFields = fields;
lines.add("--- methods ---");
for (Method method : object.getClass().getMethods()) {
if (method.getDeclaringClass() == Object.class) continue;
@@ -375,13 +396,95 @@ public final class ObjectPanel {
lines.add(" " + shortType(method.getReturnType().getName()) + " " + method.getName()
+ "(" + params + ")");
}
fillTextRows(state, lines);
fillRows(state, lines, fieldRows);
setStatus(state, "Inspected " + SceneObjects.kind(object) + " \""
+ SceneObjects.nameOf(object) + "\": " + (lines.size() - 2) + " entries");
}
});
}
/**
* 把输入框里的值写进"被点选的字段":直接反射改模型字段 + 包一次撤销 + 重建树节点,
* 然后重新 Inspect 一遍(值会立刻显出来)——这就是"改一个值看哪个字段动"的实验入口。
*/
private static void applyFieldValue(final State state) {
final java.lang.reflect.Field field = state.fieldTarget;
final Object owner = state.fieldOwner;
final String text = textFieldText(state.nameField);
if (field == null || owner == null) {
setStatus(state, "Set Value: click a field row first.");
return;
}
SceneActions.onEditorThread(state.loader, new Runnable() {
@Override
public void run() {
try {
field.setAccessible(true);
Class<?> type = field.getType();
Object value;
if (type == float.class) value = Float.valueOf(Float.parseFloat(text.trim()));
else if (type == int.class) value = Integer.valueOf(Integer.parseInt(text.trim()));
else if (type == boolean.class) value = Boolean.valueOf(Boolean.parseBoolean(text.trim()));
else if (type == double.class) value = Double.valueOf(Double.parseDouble(text.trim()));
else if (type == String.class) value = text;
else {
setStatus(state, "Set Value: unsupported type " + type.getName());
return;
}
Object skeleton = SceneActions.skeletonOf(owner);
Class<?> undo = Class.forName("un", true, state.loader);
java.lang.reflect.Method begin = null, commit = null;
for (Method m : undo.getDeclaredMethods()) {
if (!java.lang.reflect.Modifier.isStatic(m.getModifiers())) continue;
if (m.getParameterTypes().length != 0) continue;
if (m.getReturnType() != void.class) continue;
if (m.getName().equals("D")) begin = m;
if (m.getName().equals("B")) commit = m;
}
if (begin != null) begin.invoke(null);
field.set(owner, value);
if (commit != null) commit.invoke(null);
SceneActions.refresh(state.loader, skeleton);
setStatus(state, "Set " + field.getName() + " = " + text);
} catch (Throwable error) {
setStatus(state, "Set Value failed: " + error);
error.printStackTrace(System.err);
}
inspect(state);
}
});
}
/** 跟随编辑器里的选择:变了就自动重新 Inspect(做成"实时显示数据")。 */
private static void startSelectionWatcher(final State state) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Object last = null;
while (true) {
try {
Object current = SceneObjects.selected(state.loader);
if (current != null && current != last) {
last = current;
inspect(state);
}
Thread.sleep(600);
} catch (InterruptedException ignored) {
return;
} catch (Throwable error) {
try {
Thread.sleep(600);
} catch (InterruptedException ignored) {
return;
}
}
}
}
}, "spineview-selection-watch");
thread.setDaemon(true);
thread.start();
}
private static int countFields(Object object) {
int count = 0;
for (Class<?> type = object.getClass(); type != null && type != Object.class;
@@ -418,6 +521,68 @@ public final class ObjectPanel {
}
}
/**
* 填列表区:普通行是只读文本;`clickable` 里的行做成可点按钮——
* 点一下 = 选中该字段(把当前值读进输入框),配合 `Set Value` 就能改值做实验。
*/
private static void fillRows(final State state, List<String> lines,
java.util.Map<Integer, Object[]> clickable) {
try {
call(state.listTable, Obf.TABLE_CLEAR_CHILDREN, new Class<?>[0], void.class);
state.logBlockStarted = false;
Class<?> cellClass = Class.forName(Obf.CELL, true, state.loader);
Class<?> actorClass = Class.forName(Obf.ACTOR, true, state.loader);
for (int i = 0; i < lines.size(); i++) {
final Object[] target = clickable.get(Integer.valueOf(i));
Object row = button(state.loader, state.skin, lines.get(i));
Object cell = call(state.listTable, Obf.TABLE_ADD, new Class<?>[] {actorClass},
cellClass, row);
layoutCell(cell, Obf.CELL_LEFT);
call(state.listTable, Obf.TABLE_ROW, new Class<?>[0], cellClass);
if (target != null) {
final String valueText = String.valueOf(target[2]);
wire(state.loader, row, new Runnable() {
@Override
public void run() {
state.fieldOwner = target[0];
state.fieldTarget = (java.lang.reflect.Field) target[1];
setInputText(state, valueText);
setStatus(state, "Picked field " + state.fieldTarget.getName()
+ " (value " + valueText + ") -- edit the name box, then Set Value");
}
});
}
}
state.rendering = false;
appendLogRows(state);
invalidateList(state);
} catch (Throwable error) {
System.err.println("[panel] 字段列表填充失败: " + error);
}
}
/** 把文字写进面板顶部的输入框(改字段值用)。 */
private static void setInputText(final State state, final String text) {
SceneActions.onEditorThread(state.loader, new Runnable() {
@Override
public void run() {
for (Method method : state.nameField.getClass().getMethods()) {
Class<?>[] types = method.getParameterTypes();
if (types.length != 1) continue;
if (method.getReturnType() != void.class) continue;
if (types[0] != String.class && types[0] != CharSequence.class) continue;
try {
method.setAccessible(true);
method.invoke(state.nameField, text);
return;
} catch (Throwable ignored) {
// 换下一个
}
}
}
});
}
/** 用纯文本行填列表区(行不可点)。 */
private static void fillTextRows(final State state, List<String> lines) {
try {