diff --git a/src/core/region.ts b/src/core/region.ts index ed3bf76..57871a5 100644 --- a/src/core/region.ts +++ b/src/core/region.ts @@ -28,44 +28,63 @@ export type RegionAxis = { export function applyAlign(region: Region){ if (region.children.length === 0) return; - // 对每个 axis 分别处理 + // 对每个 axis 分别处理,但保持空间关系 for (let axisIndex = 0; axisIndex < region.axes.length; axisIndex++) { const axis = region.axes[axisIndex]; if (!axis.align) continue; - // 根据当前轴的位置排序 children - region.children.sort((a, b) => { - const posA = a.value.position[axisIndex] ?? 0; - const posB = b.value.position[axisIndex] ?? 0; - return posA - posB; - }); + // 收集当前轴上的所有唯一位置值,保持原有顺序 + const positionValues = new Set(); + for (const accessor of region.children) { + positionValues.add(accessor.value.position[axisIndex] ?? 0); + } + + // 排序位置值 + const sortedPositions = Array.from(positionValues).sort((a, b) => a - b); + + // 创建位置映射:原位置 -> 新位置 + const positionMap = new Map(); if (axis.align === 'start' && axis.min !== undefined) { - // 从 min 开始紧凑排列 - region.children.forEach((accessor, index) => { - accessor.value.position[axisIndex] = axis.min! + index; + // 从 min 开始紧凑排列,保持相对顺序 + sortedPositions.forEach((pos, index) => { + positionMap.set(pos, axis.min! + index); }); } else if (axis.align === 'end' && axis.max !== undefined) { // 从 max 开始向前紧凑排列 - const count = region.children.length; - region.children.forEach((accessor, index) => { - accessor.value.position[axisIndex] = axis.max! - (count - 1 - index); + const count = sortedPositions.length; + sortedPositions.forEach((pos, index) => { + positionMap.set(pos, axis.max! - (count - 1 - index)); }); } else if (axis.align === 'center') { // 居中排列 - const count = region.children.length; + const count = sortedPositions.length; const min = axis.min ?? 0; const max = axis.max ?? count - 1; const range = max - min; const center = min + range / 2; - region.children.forEach((accessor, index) => { - // 计算相对于中心的偏移 + sortedPositions.forEach((pos, index) => { const offset = index - (count - 1) / 2; - accessor.value.position[axisIndex] = center + offset; + positionMap.set(pos, center + offset); }); } + + // 应用位置映射到所有 part + for (const accessor of region.children) { + const currentPos = accessor.value.position[axisIndex] ?? 0; + accessor.value.position[axisIndex] = positionMap.get(currentPos) ?? currentPos; + } } + + // 最后按所有轴排序 children + region.children.sort((a, b) => { + for (let i = 0; i < region.axes.length; i++) { + const diff = (a.value.position[i] ?? 0) - (b.value.position[i] ?? 0); + if (diff !== 0) return diff; + } + return 0; + }); } /** diff --git a/tests/core/region.test.ts b/tests/core/region.test.ts index 4a19336..906bd56 100644 --- a/tests/core/region.test.ts +++ b/tests/core/region.test.ts @@ -159,14 +159,14 @@ describe('Region', () => { applyAlign(region); - // 第一轴 (x, axisIndex=0) 对齐: - // 排序:part3(x=2), part1(x=5), part2(x=7) → children=[part3, part1, part2] - // 对齐:part3.position[0]=0, part1.position[0]=1, part2.position[0]=2 + // X 轴对齐: + // 唯一位置值:[2, 5, 7] -> 映射到 [0, 1, 2] + // part3: 2->0, part1: 5->1, part2: 7->2 // 结果:part3=[0,30], part1=[1,10], part2=[2,20] // - // 第二轴 (y, axisIndex=1) 对齐: - // 排序:part1(y=10), part2(y=20), part3(y=30) → children=[part1, part2, part3] - // 对齐:part1.position[1]=0, part2.position[1]=1, part3.position[1]=2 + // Y 轴对齐: + // 唯一位置值:[10, 20, 30] -> 映射到 [0, 1, 2] + // part1: 10->0, part2: 20->1, part3: 30->2 // 最终:part1=[1,0], part2=[2,1], part3=[0,2] const positions = region.children.map(c => ({ @@ -174,15 +174,62 @@ describe('Region', () => { position: c.value.position })); - // children 按 y 轴排序后的顺序 + // children 按位置排序后的顺序 + expect(positions[0].id).toBe('p3'); + expect(positions[0].position).toEqual([0, 2]); + + expect(positions[1].id).toBe('p1'); + expect(positions[1].position).toEqual([1, 0]); + + expect(positions[2].id).toBe('p2'); + expect(positions[2].position).toEqual([2, 1]); + }); + + it('should align 4 elements on rectangle corners', () => { + // 4 个元素放在矩形的四个角:(0,0), (10,0), (10,1), (0,1) + // 期望:保持矩形布局,只是紧凑到 (0,0), (1,0), (1,1), (0,1) + const part1 = createPart('p1', [0, 0]); // 左下角 + const part2 = createPart('p2', [10, 0]); // 右下角 + const part3 = createPart('p3', [10, 1]); // 右上角 + const part4 = createPart('p4', [0, 1]); // 左上角 + + const region = createRegion( + [ + { name: 'x', min: 0, max: 10, align: 'start' }, + { name: 'y', min: 0, max: 10, align: 'start' } + ], + [part1, part2, part3, part4] + ); + + applyAlign(region); + + // X 轴对齐: + // 唯一位置值:[0, 10] -> 映射到 [0, 1] + // part1: 0->0, part4: 0->0, part2: 10->1, part3: 10->1 + // 结果:part1=[0,0], part4=[0,1], part2=[1,0], part3=[1,1] + // + // Y 轴对齐: + // 唯一位置值:[0, 1] -> 映射到 [0, 1] (已经是紧凑的) + // part1: 0->0, part2: 0->0, part4: 1->1, part3: 1->1 + // 最终:part1=[0,0], part2=[1,0], part4=[0,1], part3=[1,1] + + const positions = region.children.map(c => ({ + id: c.value.id, + position: c.value.position + })); + + // children 按位置排序后的顺序:(0,0), (0,1), (1,0), (1,1) expect(positions[0].id).toBe('p1'); - expect(positions[0].position).toEqual([1, 0]); + expect(positions[0].position).toEqual([0, 0]); - expect(positions[1].id).toBe('p2'); - expect(positions[1].position).toEqual([2, 1]); + expect(positions[1].id).toBe('p4'); + expect(positions[1].position).toEqual([0, 1]); - expect(positions[2].id).toBe('p3'); - expect(positions[2].position).toEqual([0, 2]); + expect(positions[2].id).toBe('p2'); + expect(positions[2].position).toEqual([1, 0]); + + expect(positions[3].id).toBe('p3'); + expect(positions[3].position).toEqual([1, 1]); }); });