Inspector 变成可测数据的入口:字段行可点(读出当前值到输入框) + Set Value 按钮直接改模型字段(带撤销+重建树节点) + 跟随编辑器选择自动重新 Inspect
This commit is contained in:
1 parent
0ec33d90ae
commit
5e7aed4a51
1 file changed
+168
-3
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user