diff --git a/client/js/01_SubGame/codes/ui/LayoutSolver.js b/client/js/01_SubGame/codes/ui/LayoutSolver.js index 5aad44e..96406b5 100644 --- a/client/js/01_SubGame/codes/ui/LayoutSolver.js +++ b/client/js/01_SubGame/codes/ui/LayoutSolver.js @@ -171,8 +171,9 @@ var EQW_LayoutSolver = EQW_LayoutSolver || { }, //attach:贴在另一个精灵上。corner 为空 = 在目标【内部】对齐;有值 = 与目标对应角重合 - //target 支持运行时注入:叫分档位贴自己的按钮、花色图标贴自己的按钮、牌角标贴"打出的那张牌", - //这类目标配置里写不出固定值,节点写成无 target 的模板,由 ctx.target 在运行时补上(ctx.target 优先于 n.target) + //target/w/h 都支持运行时注入:叫分档位贴自己的按钮、花色图标贴自己的按钮、牌角标贴"打出的那张牌", + //这类目标配置里写不出固定值;动态文字标签的真实宽高要等文案渲染出来才知道,配置里也留空。 + //这些节点写成模板,由 ctx.target/ctx.w/ctx.h 在运行时补上(ctx.* 优先于 n.* 里的同名字段) _solveAttach: function (n, ctx) { if (!ctx.rects) { throw new Error('[EQW_LayoutSolver] attach 需要 ctx.rects'); } var targetKey = ctx.target || n.target; @@ -180,7 +181,11 @@ var EQW_LayoutSolver = EQW_LayoutSolver || { 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 }; + //ctx.w/ctx.h 优先于配置里的 n.w/n.h;后续的校验、求解、返回值一律用合并后的值 + var w = (typeof ctx.w === 'number') ? ctx.w : n.w; + var h = (typeof ctx.h === 'number') ? ctx.h : n.h; + + var sprite = { width: w, height: h }; var offset = { x: n.offsetX || 0, y: n.offsetY || 0 }; var pos; @@ -191,41 +196,41 @@ var EQW_LayoutSolver = EQW_LayoutSolver || { case 'topLeft': pos = AlignmentUtils.alignCornerTopLeft(target, sprite, offset); break; case 'topRight': - this._requireW(n, targetKey, 'corner=topRight'); + this._requireW(w, targetKey, 'corner=topRight'); pos = AlignmentUtils.alignCornerTopRight(target, sprite, offset); break; case 'bottomLeft': - this._requireH(n, targetKey, 'corner=bottomLeft'); + this._requireH(h, targetKey, 'corner=bottomLeft'); pos = AlignmentUtils.alignCornerBottomLeft(target, sprite, offset); break; case 'bottomRight': - this._requireW(n, targetKey, 'corner=bottomRight'); - this._requireH(n, targetKey, 'corner=bottomRight'); + this._requireW(w, targetKey, 'corner=bottomRight'); + this._requireH(h, 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); + this._requireW(w, 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); + this._requireH(h, 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 }]; + return [{ x: pos.x, y: pos.y, width: w, height: h }]; }, - _requireW: function (n, targetKey, mode) { - if (typeof n.w !== 'number') { - throw new Error('[EQW_LayoutSolver] attach target=' + targetKey + '(' + mode + ')需要数字 w,当前缺失'); + _requireW: function (w, targetKey, mode) { + if (typeof w !== 'number') { + throw new Error('[EQW_LayoutSolver] attach target=' + targetKey + '(' + mode + ')需要数字 w:配置未写 w,且 ctx.w 未给出'); } }, - _requireH: function (n, targetKey, mode) { - if (typeof n.h !== 'number') { - throw new Error('[EQW_LayoutSolver] attach target=' + targetKey + '(' + mode + ')需要数字 h,当前缺失'); + _requireH: function (h, targetKey, mode) { + if (typeof h !== 'number') { + throw new Error('[EQW_LayoutSolver] attach target=' + targetKey + '(' + mode + ')需要数字 h:配置未写 h,且 ctx.h 未给出'); } }, diff --git a/client/tests/test_layoutsolver.js b/client/tests/test_layoutsolver.js index 759fda5..f624d84 100644 --- a/client/tests/test_layoutsolver.js +++ b/client/tests/test_layoutsolver.js @@ -170,6 +170,41 @@ t.eq('attach corner=bottomRight 缺 w 抛错', t.eq('attach corner=bottomRight 缺 h 抛错', throws(() => EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: 'bottomRight', w: 20 }, { rects: rects })), true); +// ============ attach w/h 运行时注入(ctx.w/ctx.h,供动态文字标签/牌角标等尺寸未知的模板节点用)============ +// 配置无 w、hAlign=right(用得到 sprite.width)、靠 ctx.w 补上 → 正常求解,返回 width 就是 ctx.w +const wFromCtx = EQW_LayoutSolver.solve( + { kind: 'attach', target: 'TARGET', hAlign: 'right', vAlign: 'top', h: 10 }, + { rects: rects, w: 42 } +); +t.eq('ctx.w 补全缺失的 w,正常求解', throws(() => EQW_LayoutSolver.solve( + { kind: 'attach', target: 'TARGET', hAlign: 'right', vAlign: 'top', h: 10 }, { rects: rects, w: 42 } +)), false); +t.eq('返回的 width 等于 ctx.w', wFromCtx[0].width, 42); + +// 配置有 w,同时给 ctx.w → ctx.w 优先覆盖配置值 +const wOverride = EQW_LayoutSolver.solve( + { kind: 'attach', target: 'TARGET', hAlign: 'right', vAlign: 'top', w: 20, h: 10 }, + { rects: rects, w: 99 } +); +t.eq('ctx.w 覆盖配置里的 w', wOverride[0].width, 99); + +// 配置无 h、vAlign=middle(用得到 sprite.height)、靠 ctx.h 补上 → 正常求解,返回 height 就是 ctx.h +const hFromCtx = EQW_LayoutSolver.solve( + { kind: 'attach', target: 'TARGET', hAlign: 'left', vAlign: 'middle', w: 20 }, + { rects: rects, h: 33 } +); +t.eq('返回的 height 等于 ctx.h', hFromCtx[0].height, 33); + +// 配置无 w、hAlign=right、ctx.w 也没给 → 仍然抛错(不能因为加了通道就失效) +t.eq('两处都没给 w 仍抛错', throws(() => EQW_LayoutSolver.solve( + { kind: 'attach', target: 'TARGET', hAlign: 'right', vAlign: 'top', h: 10 }, { rects: rects } +)), true); + +// ctx.w/ctx.h 对不需要该维度的对齐模式(hAlign=left)不产生影响,仍正常求解 +t.eq('hAlign=left 时给不给 ctx.w 都不影响', throws(() => EQW_LayoutSolver.solve( + { kind: 'attach', target: 'TARGET', hAlign: 'left', vAlign: 'top' }, { rects: rects, w: 7 } +)), false); + // ============ attach target 运行时注入(ctx.target,供叫分档位/花色图标/牌角标等模板节点用)============ const rects2 = { TARGET: { x: 100, y: 200, width: 80, height: 40 }, OTHER: { x: 5, y: 6, width: 10, height: 10 } };