diff --git a/client/js/01_SubGame/codes/ui/LayoutSolver.js b/client/js/01_SubGame/codes/ui/LayoutSolver.js index 1dbdeff..a5cf0b0 100644 --- a/client/js/01_SubGame/codes/ui/LayoutSolver.js +++ b/client/js/01_SubGame/codes/ui/LayoutSolver.js @@ -137,7 +137,17 @@ var EQW_LayoutSolver = EQW_LayoutSolver || { //grid:固定行列的按钮阵,逐行调 distributeHorizontally(清单 §6.1) _solveGrid: function (n, ctx) { - var count = (typeof ctx.count === 'number') ? ctx.count : (n.cols * n.rows); + //fillOrder 目前只实现行优先;不写等同 row,写了别的值一律显式拒绝(YAGNI,不做无人用的列优先) + if (n.fillOrder && n.fillOrder !== 'row') { + throw new Error('[EQW_LayoutSolver] grid 仅支持 fillOrder=row,暂不支持: ' + n.fillOrder); + } + + var capacity = n.cols * n.rows; + var count = (typeof ctx.count === 'number') ? ctx.count : capacity; + //配置与运行时项数不一致时必须显式报错,不能静默截断多出的项(工程总则 §7 显式失败) + if (count > capacity) { + throw new Error('[EQW_LayoutSolver] grid 项数超出容量: count=' + count + ' > cols(' + n.cols + ')*rows(' + n.rows + ')=' + capacity); + } if (count <= 0) { return []; } var out = []; diff --git a/client/tests/test_layoutsolver.js b/client/tests/test_layoutsolver.js index f998c9a..c4d428b 100644 --- a/client/tests/test_layoutsolver.js +++ b/client/tests/test_layoutsolver.js @@ -87,6 +87,21 @@ t.eq('grid 第8项 y', g[7].y, 160 + 56 + 24); // count 少于满格时只出 count 个 t.eq('grid 不足一行', EQW_LayoutSolver.solve(grid, { count: 3 }).length, 3); +// 边界:count 恰好等于容量(cols*rows=14)时正常返回,不抛错 +t.eq('grid 恰好满格不抛错', EQW_LayoutSolver.solve(grid, { count: 14 }).length, 14); + +// count 超出容量:显式抛错,不静默截断多出的项 +t.eq('grid 超载抛错', throws(() => EQW_LayoutSolver.solve(grid, { count: 15 })), true); + +// fillOrder 校验:目前只支持行优先(YAGNI,未实现列优先),不写等同 row +const gridNoFillOrder = { kind: 'grid', anchorX: 640, anchorY: 160, cols: 7, rows: 2, + itemWidth: 74, itemHeight: 56, spacingX: 7, spacingY: 24, anchor: 'center' }; +t.eq('grid 不写 fillOrder 正常', EQW_LayoutSolver.solve(gridNoFillOrder, { count: 14 }).length, 14); +t.eq('grid fillOrder=row 正常', EQW_LayoutSolver.solve(grid, { count: 14 }).length, 14); +const gridColumnOrder = { kind: 'grid', anchorX: 640, anchorY: 160, cols: 7, rows: 2, + itemWidth: 74, itemHeight: 56, spacingX: 7, spacingY: 24, anchor: 'center', fillOrder: 'column' }; +t.eq('grid fillOrder=column 抛错', throws(() => EQW_LayoutSolver.solve(gridColumnOrder, { count: 14 })), true); + // ============ attach 内部对齐(corner 为空)============ const rects = { TARGET: { x: 100, y: 200, width: 80, height: 40 } }; const attachIn = { kind: 'attach', target: 'TARGET', hAlign: 'right', vAlign: 'top', @@ -107,12 +122,29 @@ const attachCorner = { kind: 'attach', target: 'TARGET', corner: 'topRight', const a2 = EQW_LayoutSolver.solve(attachCorner, { rects: rects }); t.eq('attach 外侧右上角', a2, [{ x: 100 + 80 - 20 + 4, y: 200 + 6, width: 20, height: 10 }]); -// 四个角都要能解 +// 四个角都要能解(通用检查:长度与类型) ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'].forEach(c => { const r = EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: c, w: 20, h: 10 }, { rects: rects }); t.eq('attach 角 ' + c + ' 可解', r.length === 1 && typeof r[0].x === 'number', true); }); +// 四角精确坐标核对:target={x:100,y:200,width:80,height:40},sprite={w:20,h:10},offset 缺省=0 +// topLeft(AlignmentUtils.js:266-272):x=target.x=100,y=target.y=200 +const cTopLeft = EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: 'topLeft', w: 20, h: 10 }, { rects: rects }); +t.eq('attach 角 topLeft 精确坐标', cTopLeft, [{ x: 100, y: 200, width: 20, height: 10 }]); + +// topRight(AlignmentUtils.js:292-298):x=target.x+target.width-sprite.width=100+80-20=160,y=target.y=200 +const cTopRight = EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: 'topRight', w: 20, h: 10 }, { rects: rects }); +t.eq('attach 角 topRight 精确坐标', cTopRight, [{ x: 160, y: 200, width: 20, height: 10 }]); + +// bottomLeft(AlignmentUtils.js:318-324):x=target.x=100,y=target.y+target.height-sprite.height=200+40-10=230 +const cBottomLeft = EQW_LayoutSolver.solve({ kind: 'attach', target: 'TARGET', corner: 'bottomLeft', w: 20, h: 10 }, { rects: rects }); +t.eq('attach 角 bottomLeft 精确坐标', cBottomLeft, [{ x: 100, y: 230, width: 20, height: 10 }]); + +// bottomRight(AlignmentUtils.js:344-350):x=target.x+target.width-sprite.width=100+80-20=160,y=同 bottomLeft=230 +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 }]); + // ============ bySeat 合并:列出的覆盖、未列出的继承 ============ const bySeat = { kind: 'fan', direction: 'horizontal', itemWidth: 90, itemHeight: 155,