From 55c0794ed44513976d688fd57ea1e930398fd584 Mon Sep 17 00:00:00 2001 From: tianmo Date: Fri, 16 Oct 2026 16:26:15 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E6=81=A2=E5=A4=8D=E6=88=90?= =?UTF-8?q?=E4=B8=8A/=E4=B8=AD/=E4=B8=8B=E4=B8=89=E6=AE=B5=EF=BC=9A?= =?UTF-8?q?=E4=B8=8A=3D=E6=8C=89=E9=92=AE=E5=9D=97(=E4=B8=A4=E5=88=97?= =?UTF-8?q?=E7=B4=A7=E9=82=BB+=E5=90=8D=E5=AD=97=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E6=A1=86)=E3=80=81=E4=B8=AD=3D=E5=88=97=E8=A1=A8(expand+fill)?= =?UTF-8?q?=E3=80=81=E4=B8=8B=3D=E6=97=A5=E5=BF=97(=E7=8B=AC=E7=AB=8B=20La?= =?UTF-8?q?bel=EF=BC=8C=E8=87=AA=E7=84=B6=E9=AB=98=E5=BA=A6=E8=B4=B4?= =?UTF-8?q?=E5=BA=95)=EF=BC=9B=E5=8E=BB=E6=8E=89=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E9=87=8C=E5=86=85=E5=B5=8C=E7=9A=84=E6=97=A5=E5=BF=97=E8=A1=8C?= =?UTF-8?q?=E4=B8=8E=E4=B9=8B=E5=89=8D=E6=9C=AA=E9=AA=8C=E8=AF=81=E7=9A=84?= =?UTF-8?q?=E5=B0=BA=E5=AF=B8=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- viewpanel/src/com/spineview/ObjectPanel.java | 124 +++++++++++-------- 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/viewpanel/src/com/spineview/ObjectPanel.java b/viewpanel/src/com/spineview/ObjectPanel.java index 2db3522..fecdb95 100644 --- a/viewpanel/src/com/spineview/ObjectPanel.java +++ b/viewpanel/src/com/spineview/ObjectPanel.java @@ -50,6 +50,7 @@ public final class ObjectPanel { List objects = new ArrayList(); 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"取)。 */