面板恢复成上/中/下三段:上=按钮块(两列紧邻+名字输入框)、中=列表(expand+fill)、下=日志(独立 Label,自然高度贴底);去掉列表里内嵌的日志行与之前未验证的尺寸调用

This commit is contained in:
tianmo committed 2026-10-16 16:26:15 +08:00
1 parent 4b05804479
commit 55c0794ed4
1 file changed
+75 -49
+75 -49
View File
@@ -50,6 +50,7 @@ public final class ObjectPanel {
List<Object> objects = new ArrayList<Object>();
Object target;
boolean scrollFocusLogged;
boolean geometryDumped;
Class<?> tableClass;
Class<?> scrollClass;
Class<?> actorClass;
@@ -116,58 +117,29 @@ public final class ObjectPanel {
// 按钮直接放进根表格:两行按钮 + 一行滚动列表(与阶段四验证过的结构一致)
Object[] top = {refresh, newBone, newSlot, newBBox, newPath, delete, rename, inspect, setValue};
Object buttons = tableClass.getConstructor().newInstance();
call(buttons, Obf.TABLE_TOP, new Class<?>[0], tableClass);
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);
if (i % 2 == 1 || i == top.length - 1) {
call(root, Obf.TABLE_ROW, new Class<?>[0], cellClass);
}
Object buttonCell = call(buttons, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, top[i]);
layoutCell(buttonCell, Obf.CELL_LEFT);
if (i % 2 == 1) call(buttons, Obf.TABLE_ROW, new Class<?>[0], cellClass);
}
Object cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, state.nameField);
call(buttons, Obf.TABLE_ROW, new Class<?>[0], cellClass);
Object nameCell = call(buttons, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, state.nameField);
layoutCell(nameCell, Obf.CELL_LEFT);
// 上段:按钮块(自适应高度,紧贴顶部)
Object cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, buttons);
layoutCell(cell, Obf.CELL_LEFT);
call(root, Obf.TABLE_ROW, new Class<?>[0], cellClass);
// 唯一 expand+fill 的单元格:内容区(上面是列表/Inspector,最下面是日志块,同一个滚动区)
// 中段:列表(唯一 expand+fill 的行,吃掉剩余高度)
cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, scroll);
layoutCell(cell, Obf.CELL_TOP, Obf.CELL_EXPAND, Obf.CELL_FILL);
state.listCell = cell;
try {
// 给列表一个**固定高度**:这样 ScrollPane 必定比内容矮,可滚范围(maxY)一定 > 0。
// Cell.height(float) 的载荷名是 `I`(描述符 (F)LdR;,MapMethods 指纹确认)。
call(cell, "I", new Class<?>[] {float.class}, cellClass, Float.valueOf(360f));
} catch (Throwable error) {
System.err.println("[panel] 列表固定高度设置失败: " + error);
}
// 下段:日志(独立一行,自然高度贴底;不用任何未验证的尺寸方法)
call(root, Obf.TABLE_ROW, new Class<?>[0], cellClass);
cell = call(root, Obf.TABLE_ADD, new Class<?>[] {actorClass}, cellClass, state.status);
layoutCell(cell, Obf.CELL_LEFT);
wire(loader, refresh, new Runnable() {
@Override
public void run() {
refreshList(state);
}
});
wire(loader, newBone, new Runnable() {
@Override
public void run() {
create(state, "Bone");
}
});
wire(loader, newSlot, new Runnable() {
@Override
public void run() {
create(state, "Slot");
}
});
wire(loader, newBBox, new Runnable() {
@Override
public void run() {
create(state, "Bounding Box");
}
});
wire(loader, newPath, new Runnable() {
@Override
public void run() {
create(state, "Path");
}
});
wire(loader, setValue, new Runnable() {
@Override
public void run() {
@@ -215,6 +187,29 @@ public final class ObjectPanel {
return root;
}
/** 把日志缓冲写进底部 Label(多行)。 */
private static void writeLogLabel(final State state) {
if (state.status == null) return;
final StringBuilder joined = new StringBuilder();
synchronized (state.log) {
for (String line : state.log) {
if (joined.length() > 0) joined.append('\n');
joined.append(line);
}
}
SceneActions.onEditorThread(state.loader, new Runnable() {
@Override
public void run() {
try {
call(state.status, Obf.LABEL_SET_TEXT, new Class<?>[] {CharSequence.class},
void.class, joined.toString());
} catch (Throwable error) {
System.err.println("[panel] 日志 Label 更新失败: " + error);
}
}
});
}
/** 只往内容表格末尾追加**一行**日志(必要时先补一行分隔标记)。 */
private static void appendLogRow(State state, String text) {
try {
@@ -319,8 +314,21 @@ public final class ObjectPanel {
+ (state.target == null ? "" : "; target = " + SceneObjects.kind(state.target)
+ " " + SceneObjects.nameOf(state.target)));
state.rendering = false;
appendLogRows(state);
invalidateList(state);
if (!state.geometryDumped) {
state.geometryDumped = true;
dumpFloats("pane(before)", state.listScroll);
for (String candidate : new String[] {"I", "L", "B", "Z", "V", "C", "S"}) {
try {
call(state.listCell, candidate, new Class<?>[] {float.class}, state.cellClass,
Float.valueOf(400f));
System.err.println("[panel] tried Cell." + candidate + "(400)");
dumpFloats("pane(after " + candidate + ")", state.listScroll);
} catch (Throwable ignored) {
// 该名字上没有 (F)Cell 方法
}
}
}
}
});
}
@@ -621,6 +629,26 @@ public final class ObjectPanel {
}
}
/** 只读诊断:打印对象上所有**无参返回 float**的方法名和值(用来看 ScrollPane 的真实宽高)。 */
static void dumpFloats(String tag, Object target) {
StringBuilder out = new StringBuilder("[panel] " + tag + ": ");
try {
for (Method method : target.getClass().getMethods()) {
if (method.getParameterTypes().length != 0) continue;
if (method.getReturnType() != float.class) continue;
try {
method.setAccessible(true);
out.append(method.getName()).append('=').append(method.invoke(target)).append(" ");
} catch (Throwable ignored) {
// 跳过
}
}
System.err.println(out.toString());
} catch (Throwable error) {
System.err.println("[panel] dumpFloats 失败: " + error);
}
}
static void attachEventProbe(final Object actor, final String tag) {
try {
Class<?> listenerClass = Class.forName("dn", true, actor.getClass().getClassLoader());
@@ -791,7 +819,6 @@ public final class ObjectPanel {
}
}
state.rendering = false;
appendLogRows(state);
invalidateList(state);
} catch (Throwable error) {
System.err.println("[panel] 字段列表填充失败: " + error);
@@ -834,7 +861,6 @@ public final class ObjectPanel {
call(state.listTable, Obf.TABLE_ROW, new Class<?>[0], cellClass);
}
state.rendering = false;
appendLogRows(state);
invalidateList(state);
} catch (Throwable error) {
System.err.println("[panel] inspector 填充失败: " + error);
@@ -858,8 +884,8 @@ public final class ObjectPanel {
state.log.addLast(text);
while (state.log.size() > LOG_LINES) state.log.removeFirst();
}
// 只追加一行日志:千万不要在这里重跑列表渲染,否则 Inspect 写好的字段清单会被覆盖
if (!state.rendering) appendLogRow(state, text);
// 写进底部日志 Label(多行、只保留最近几条)——不再动列表内容
writeLogLabel(state);
}
/** TextField 里的文字(名字被混淆了,按"无参 + 返回 String"取)。 */