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>();
|
final java.util.Deque<String> log = new java.util.ArrayDeque<String>();
|
||||||
boolean rendering;
|
boolean rendering;
|
||||||
boolean logBlockStarted;
|
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>();
|
List<Object> objects = new ArrayList<Object>();
|
||||||
Object target;
|
Object target;
|
||||||
}
|
}
|
||||||
@@ -95,9 +99,10 @@ public final class ObjectPanel {
|
|||||||
Object delete = button(loader, skin, "Delete");
|
Object delete = button(loader, skin, "Delete");
|
||||||
Object rename = button(loader, skin, "Rename");
|
Object rename = button(loader, skin, "Rename");
|
||||||
Object inspect = button(loader, skin, "Inspect");
|
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++) {
|
for (int i = 0; i < top.length; i++) {
|
||||||
Object cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, top[i]);
|
Object cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, top[i]);
|
||||||
layoutCell(cell, Obf.CELL_LEFT);
|
layoutCell(cell, Obf.CELL_LEFT);
|
||||||
@@ -142,6 +147,12 @@ public final class ObjectPanel {
|
|||||||
create(state, "Path");
|
create(state, "Path");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
wire(loader, setValue, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
applyFieldValue(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
wire(loader, inspect, new Runnable() {
|
wire(loader, inspect, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -177,6 +188,7 @@ public final class ObjectPanel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
startSelectionWatcher(state);
|
||||||
System.err.println("[panel] 增删改查面板已建好");
|
System.err.println("[panel] 增删改查面板已建好");
|
||||||
refreshList(state);
|
refreshList(state);
|
||||||
return root;
|
return root;
|
||||||
@@ -343,9 +355,14 @@ public final class ObjectPanel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<String> lines = new ArrayList<String>();
|
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)
|
lines.add("selected: " + SceneObjects.kind(object) + " \"" + SceneObjects.nameOf(object)
|
||||||
+ "\" (" + object.getClass().getName() + ")");
|
+ "\" (" + 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;
|
for (Class<?> type = object.getClass(); type != null && type != Object.class;
|
||||||
type = type.getSuperclass()) {
|
type = type.getSuperclass()) {
|
||||||
for (java.lang.reflect.Field field : type.getDeclaredFields()) {
|
for (java.lang.reflect.Field field : type.getDeclaredFields()) {
|
||||||
@@ -360,10 +377,14 @@ public final class ObjectPanel {
|
|||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
value = "<" + error.getClass().getSimpleName() + ">";
|
value = "<" + error.getClass().getSimpleName() + ">";
|
||||||
}
|
}
|
||||||
|
final int rowIndex = lines.size();
|
||||||
lines.add(" " + shortType(field.getType().getName()) + " " + field.getName()
|
lines.add(" " + shortType(field.getType().getName()) + " " + field.getName()
|
||||||
+ " = " + value + " @" + type.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 ---");
|
lines.add("--- methods ---");
|
||||||
for (Method method : object.getClass().getMethods()) {
|
for (Method method : object.getClass().getMethods()) {
|
||||||
if (method.getDeclaringClass() == Object.class) continue;
|
if (method.getDeclaringClass() == Object.class) continue;
|
||||||
@@ -375,13 +396,95 @@ public final class ObjectPanel {
|
|||||||
lines.add(" " + shortType(method.getReturnType().getName()) + " " + method.getName()
|
lines.add(" " + shortType(method.getReturnType().getName()) + " " + method.getName()
|
||||||
+ "(" + params + ")");
|
+ "(" + params + ")");
|
||||||
}
|
}
|
||||||
fillTextRows(state, lines);
|
fillRows(state, lines, fieldRows);
|
||||||
setStatus(state, "Inspected " + SceneObjects.kind(object) + " \""
|
setStatus(state, "Inspected " + SceneObjects.kind(object) + " \""
|
||||||
+ SceneObjects.nameOf(object) + "\": " + (lines.size() - 2) + " entries");
|
+ 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) {
|
private static int countFields(Object object) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (Class<?> type = object.getClass(); type != null && type != Object.class;
|
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) {
|
private static void fillTextRows(final State state, List<String> lines) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in new issue
Block a user