二七王: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:
@@ -171,30 +171,64 @@ var EQW_LayoutSolver = EQW_LayoutSolver || {
|
|||||||
},
|
},
|
||||||
|
|
||||||
//attach:贴在另一个精灵上。corner 为空 = 在目标【内部】对齐;有值 = 与目标对应角重合
|
//attach:贴在另一个精灵上。corner 为空 = 在目标【内部】对齐;有值 = 与目标对应角重合
|
||||||
|
//target 支持运行时注入:叫分档位贴自己的按钮、花色图标贴自己的按钮、牌角标贴"打出的那张牌",
|
||||||
|
//这类目标配置里写不出固定值,节点写成无 target 的模板,由 ctx.target 在运行时补上(ctx.target 优先于 n.target)
|
||||||
_solveAttach: function (n, ctx) {
|
_solveAttach: function (n, ctx) {
|
||||||
if (!ctx.rects) { throw new Error('[EQW_LayoutSolver] attach 需要 ctx.rects'); }
|
if (!ctx.rects) { throw new Error('[EQW_LayoutSolver] attach 需要 ctx.rects'); }
|
||||||
var target = ctx.rects[n.target];
|
var targetKey = ctx.target || n.target;
|
||||||
if (!target) { throw new Error('[EQW_LayoutSolver] attach 的 target 未在 ctx.rects 中: ' + 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 sprite = { width: n.w, height: n.h };
|
||||||
var offset = { x: n.offsetX || 0, y: n.offsetY || 0 };
|
var offset = { x: n.offsetX || 0, y: n.offsetY || 0 };
|
||||||
var pos;
|
var pos;
|
||||||
|
|
||||||
|
//w/h 是否必填取决于本次实际走的对齐分支是否用到精灵尺寸(AlignmentUtils 源码逐分支核对):
|
||||||
|
//纯文字标签在 left/top/bottom 对齐下不需要尺寸,一刀切要求 w/h 会逼配置去编造假数值
|
||||||
if (n.corner) {
|
if (n.corner) {
|
||||||
switch (n.corner) {
|
switch (n.corner) {
|
||||||
case 'topLeft': pos = AlignmentUtils.alignCornerTopLeft(target, sprite, offset); break;
|
case 'topLeft':
|
||||||
case 'topRight': pos = AlignmentUtils.alignCornerTopRight(target, sprite, offset); break;
|
pos = AlignmentUtils.alignCornerTopLeft(target, sprite, offset); break;
|
||||||
case 'bottomLeft': pos = AlignmentUtils.alignCornerBottomLeft(target, sprite, offset); break;
|
case 'topRight':
|
||||||
case 'bottomRight': pos = AlignmentUtils.alignCornerBottomRight(target, sprite, offset); break;
|
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);
|
default: throw new Error('[EQW_LayoutSolver] 未知 corner: ' + n.corner);
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
pos = AlignmentUtils.alignTo(target, sprite, n.hAlign, n.vAlign, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [{ x: pos.x, y: pos.y, width: n.w, height: n.h }];
|
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},这里补上尺寸
|
//distribute* 只返回 {x,y},这里补上尺寸
|
||||||
_withSize: function (positions, w, h) {
|
_withSize: function (positions, w, h) {
|
||||||
var out = [];
|
var out = [];
|
||||||
|
|||||||
@@ -145,6 +145,48 @@ t.eq('attach 角 bottomLeft 精确坐标', cBottomLeft, [{ x: 100, y: 230, width
|
|||||||
const cBottomRight = EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: 'bottomRight', w: 20, h: 10 }, { rects: rects });
|
const cBottomRight = EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: 'bottomRight', w: 20, h: 10 }, { rects: rects });
|
||||||
t.eq('attach 角 bottomRight 精确坐标', cBottomRight, [{ x: 160, y: 230, width: 20, height: 10 }]);
|
t.eq('attach 角 bottomRight 精确坐标', cBottomRight, [{ x: 160, y: 230, width: 20, height: 10 }]);
|
||||||
|
|
||||||
|
// ============ attach:按实际对齐模式校验 w/h(缺失且用得到时抛错,用不到时不强制) ============
|
||||||
|
// hAlign=right 用到 sprite.width(AlignmentUtils.js:111),缺 w 抛错;hAlign=left(101行)不用,缺 w 不抛错
|
||||||
|
t.eq('attach hAlign=right 缺 w 抛错',
|
||||||
|
throws(() => EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', hAlign: 'right', vAlign: 'top', h: 10 }, { rects: rects })), true);
|
||||||
|
t.eq('attach hAlign=left 缺 w 不抛错',
|
||||||
|
throws(() => EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', hAlign: 'left', vAlign: 'top', h: 10 }, { rects: rects })), false);
|
||||||
|
|
||||||
|
// vAlign=middle 用到 sprite.height(127行),缺 h 抛错;vAlign=top(122行)不用,缺 h 不抛错
|
||||||
|
t.eq('attach vAlign=middle 缺 h 抛错',
|
||||||
|
throws(() => EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', hAlign: 'left', vAlign: 'middle', w: 20 }, { rects: rects })), true);
|
||||||
|
t.eq('attach vAlign=top 缺 h 不抛错',
|
||||||
|
throws(() => EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', hAlign: 'left', vAlign: 'top', w: 20 }, { rects: rects })), false);
|
||||||
|
|
||||||
|
// vAlign=bottom(132行)用的是 target.height,不是 sprite.height,缺 h 不应抛错——这条容易想当然写错
|
||||||
|
t.eq('attach vAlign=bottom 缺 h 不抛错',
|
||||||
|
throws(() => EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', hAlign: 'left', vAlign: 'bottom', w: 20 }, { rects: rects })), false);
|
||||||
|
|
||||||
|
// corner=topLeft(266-272行)不用 w/h,都缺不抛错;corner=bottomRight(344-350行)两者都用,缺一即抛错
|
||||||
|
t.eq('attach corner=topLeft 缺 w/h 不抛错',
|
||||||
|
throws(() => EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: 'topLeft' }, { rects: rects })), false);
|
||||||
|
t.eq('attach corner=bottomRight 缺 w 抛错',
|
||||||
|
throws(() => EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: 'bottomRight', h: 10 }, { rects: rects })), true);
|
||||||
|
t.eq('attach corner=bottomRight 缺 h 抛错',
|
||||||
|
throws(() => EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: 'bottomRight', w: 20 }, { rects: rects })), true);
|
||||||
|
|
||||||
|
// ============ attach target 运行时注入(ctx.target,供叫分档位/花色图标/牌角标等模板节点用)============
|
||||||
|
const rects2 = { TARGET: { x: 100, y: 200, width: 80, height: 40 }, OTHER: { x: 5, y: 6, width: 10, height: 10 } };
|
||||||
|
|
||||||
|
// ctx.target 覆盖配置里写死的 target
|
||||||
|
const attachOverride = { kind: 'attach', target: 'TARGET', corner: 'topLeft' };
|
||||||
|
const aOverride = EQW_LayoutSolver.solve(attachOverride, { rects: rects2, target: 'OTHER' });
|
||||||
|
t.eq('ctx.target 覆盖配置 target', { x: aOverride[0].x, y: aOverride[0].y }, { x: 5, y: 6 });
|
||||||
|
|
||||||
|
// 配置无 target(模板节点)时,ctx.target 生效
|
||||||
|
const attachTemplate = { kind: 'attach', corner: 'topLeft' };
|
||||||
|
const aFromCtxOnly = EQW_LayoutSolver.solve(attachTemplate, { rects: rects, target: 'TARGET' });
|
||||||
|
t.eq('配置无 target 时 ctx.target 生效', { x: aFromCtxOnly[0].x, y: aFromCtxOnly[0].y }, { x: 100, y: 200 });
|
||||||
|
|
||||||
|
// 配置与 ctx 都没给 target:抛错
|
||||||
|
t.eq('target 与 ctx.target 都缺失抛错',
|
||||||
|
throws(() => EQW_LayoutSolver.solve(attachTemplate, { rects: rects })), true);
|
||||||
|
|
||||||
// ============ bySeat 合并:列出的覆盖、未列出的继承 ============
|
// ============ bySeat 合并:列出的覆盖、未列出的继承 ============
|
||||||
const bySeat = {
|
const bySeat = {
|
||||||
kind: 'fan', direction: 'horizontal', itemWidth: 90, itemHeight: 155,
|
kind: 'fan', direction: 'horizontal', itemWidth: 90, itemHeight: 155,
|
||||||
|
|||||||
Reference in New Issue
Block a user