二七王:LayoutSolver attach 支持运行时 target 注入,按对齐模式校验 w/h

配置落地后暴露两个缺口:叫分档位/花色图标/牌角标等 attach 目标要运行时
才确定,配置写不出固定 target,需要 ctx.target 覆盖/补全(优先于 n.target,
两者皆无则抛错);纯文字标签没有固定 w/h,此前缺失时会静默算出 NaN 坐标。
改为按本次实际走的对齐分支(hAlign/vAlign/corner)逐一核对 AlignmentUtils
源码确认是否用到 sprite.width/height,只在用得到时才要求对应字段是数字,
不用得到时(如 left/top/bottom、topLeft)不强制,避免逼纯文字配置编造假值。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 21:29:31 +08:00
co-authored by Claude Opus 5
parent 6c92920df8
commit 91f0eb116c
2 changed files with 82 additions and 6 deletions
+40 -6
View File
@@ -171,30 +171,64 @@ var EQW_LayoutSolver = EQW_LayoutSolver || {
},
//attach:贴在另一个精灵上。corner 为空 = 在目标【内部】对齐;有值 = 与目标对应角重合
//target 支持运行时注入:叫分档位贴自己的按钮、花色图标贴自己的按钮、牌角标贴"打出的那张牌",
//这类目标配置里写不出固定值,节点写成无 target 的模板,由 ctx.target 在运行时补上(ctx.target 优先于 n.target)
_solveAttach: function (n, ctx) {
if (!ctx.rects) { throw new Error('[EQW_LayoutSolver] attach 需要 ctx.rects'); }
var target = ctx.rects[n.target];
if (!target) { throw new Error('[EQW_LayoutSolver] attach 的 target 未在 ctx.rects 中: ' + n.target); }
var targetKey = ctx.target || n.target;
if (!targetKey) { throw new Error('[EQW_LayoutSolver] attach 配置未写 target,且 ctx.target 未给出'); }
var target = ctx.rects[targetKey];
if (!target) { throw new Error('[EQW_LayoutSolver] attach 的 target 未在 ctx.rects 中: ' + targetKey); }
var sprite = { width: n.w, height: n.h };
var offset = { x: n.offsetX || 0, y: n.offsetY || 0 };
var pos;
//w/h 是否必填取决于本次实际走的对齐分支是否用到精灵尺寸(AlignmentUtils 源码逐分支核对):
//纯文字标签在 left/top/bottom 对齐下不需要尺寸,一刀切要求 w/h 会逼配置去编造假数值
if (n.corner) {
switch (n.corner) {
case 'topLeft': pos = AlignmentUtils.alignCornerTopLeft(target, sprite, offset); break;
case 'topRight': pos = AlignmentUtils.alignCornerTopRight(target, sprite, offset); break;
case 'bottomLeft': pos = AlignmentUtils.alignCornerBottomLeft(target, sprite, offset); break;
case 'bottomRight': pos = AlignmentUtils.alignCornerBottomRight(target, sprite, offset); break;
case 'topLeft':
pos = AlignmentUtils.alignCornerTopLeft(target, sprite, offset); break;
case 'topRight':
this._requireW(n, targetKey, 'corner=topRight');
pos = AlignmentUtils.alignCornerTopRight(target, sprite, offset); break;
case 'bottomLeft':
this._requireH(n, targetKey, 'corner=bottomLeft');
pos = AlignmentUtils.alignCornerBottomLeft(target, sprite, offset); break;
case 'bottomRight':
this._requireW(n, targetKey, 'corner=bottomRight');
this._requireH(n, targetKey, 'corner=bottomRight');
pos = AlignmentUtils.alignCornerBottomRight(target, sprite, offset); break;
default: throw new Error('[EQW_LayoutSolver] 未知 corner: ' + n.corner);
}
} else {
//hAlign: center/right 要用 sprite.width;left 不用(AlignmentUtils.js 第106/111/101行)
if (n.hAlign === 'center' || n.hAlign === 'right') {
this._requireW(n, targetKey, 'hAlign=' + n.hAlign);
}
//vAlign: 只有 middle 用 sprite.height;top/bottom 都不用(bottom 用的是 target.height,第132行)
if (n.vAlign === 'middle') {
this._requireH(n, targetKey, 'vAlign=' + n.vAlign);
}
pos = AlignmentUtils.alignTo(target, sprite, n.hAlign, n.vAlign, offset);
}
return [{ x: pos.x, y: pos.y, width: n.w, height: n.h }];
},
_requireW: function (n, targetKey, mode) {
if (typeof n.w !== 'number') {
throw new Error('[EQW_LayoutSolver] attach target=' + targetKey + '(' + mode + ')需要数字 w,当前缺失');
}
},
_requireH: function (n, targetKey, mode) {
if (typeof n.h !== 'number') {
throw new Error('[EQW_LayoutSolver] attach target=' + targetKey + '(' + mode + ')需要数字 h,当前缺失');
}
},
//distribute* 只返回 {x,y},这里补上尺寸
_withSize: function (positions, w, h) {
var out = [];