167 lines
4.3 KiB
TypeScript
167 lines
4.3 KiB
TypeScript
import { Position, Size } from "@/types/screen";
|
|
|
|
export interface GridSettings {
|
|
columns: number;
|
|
gap: number;
|
|
padding: number;
|
|
snapToGrid: boolean;
|
|
}
|
|
|
|
export interface GridInfo {
|
|
columnWidth: number;
|
|
totalWidth: number;
|
|
totalHeight: number;
|
|
}
|
|
|
|
/**
|
|
* 격자 정보 계산
|
|
*/
|
|
export function calculateGridInfo(
|
|
containerWidth: number,
|
|
containerHeight: number,
|
|
gridSettings: GridSettings,
|
|
): GridInfo {
|
|
const { columns, gap, padding } = gridSettings;
|
|
|
|
// 사용 가능한 너비 계산 (패딩 제외)
|
|
const availableWidth = containerWidth - padding * 2;
|
|
|
|
// 격자 간격을 고려한 컬럼 너비 계산
|
|
const totalGaps = (columns - 1) * gap;
|
|
const columnWidth = (availableWidth - totalGaps) / columns;
|
|
|
|
return {
|
|
columnWidth: Math.max(columnWidth, 50), // 최소 50px
|
|
totalWidth: containerWidth,
|
|
totalHeight: containerHeight,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 위치를 격자에 맞춤
|
|
*/
|
|
export function snapToGrid(position: Position, gridInfo: GridInfo, gridSettings: GridSettings): Position {
|
|
if (!gridSettings.snapToGrid) {
|
|
return position;
|
|
}
|
|
|
|
const { columnWidth } = gridInfo;
|
|
const { gap, padding } = gridSettings;
|
|
|
|
// 격자 기준으로 위치 계산
|
|
const gridX = Math.round((position.x - padding) / (columnWidth + gap));
|
|
const gridY = Math.round((position.y - padding) / 20); // 20px 단위로 세로 스냅
|
|
|
|
// 실제 픽셀 위치로 변환
|
|
const snappedX = Math.max(padding, padding + gridX * (columnWidth + gap));
|
|
const snappedY = Math.max(padding, padding + gridY * 20);
|
|
|
|
return {
|
|
x: snappedX,
|
|
y: snappedY,
|
|
z: position.z,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 크기를 격자에 맞춤
|
|
*/
|
|
export function snapSizeToGrid(size: Size, gridInfo: GridInfo, gridSettings: GridSettings): Size {
|
|
if (!gridSettings.snapToGrid) {
|
|
return size;
|
|
}
|
|
|
|
const { columnWidth } = gridInfo;
|
|
const { gap } = gridSettings;
|
|
|
|
// 격자 단위로 너비 계산
|
|
const gridColumns = Math.max(1, Math.round(size.width / (columnWidth + gap)));
|
|
const snappedWidth = gridColumns * columnWidth + (gridColumns - 1) * gap;
|
|
|
|
// 높이는 20px 단위로 스냅
|
|
const snappedHeight = Math.max(40, Math.round(size.height / 20) * 20);
|
|
|
|
return {
|
|
width: Math.max(columnWidth, snappedWidth),
|
|
height: snappedHeight,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 격자 컬럼 수로 너비 계산
|
|
*/
|
|
export function calculateWidthFromColumns(columns: number, gridInfo: GridInfo, gridSettings: GridSettings): number {
|
|
const { columnWidth } = gridInfo;
|
|
const { gap } = gridSettings;
|
|
|
|
return columns * columnWidth + (columns - 1) * gap;
|
|
}
|
|
|
|
/**
|
|
* 너비에서 격자 컬럼 수 계산
|
|
*/
|
|
export function calculateColumnsFromWidth(width: number, gridInfo: GridInfo, gridSettings: GridSettings): number {
|
|
const { columnWidth } = gridInfo;
|
|
const { gap } = gridSettings;
|
|
|
|
return Math.max(1, Math.round((width + gap) / (columnWidth + gap)));
|
|
}
|
|
|
|
/**
|
|
* 격자 가이드라인 생성
|
|
*/
|
|
export function generateGridLines(
|
|
containerWidth: number,
|
|
containerHeight: number,
|
|
gridSettings: GridSettings,
|
|
): {
|
|
verticalLines: number[];
|
|
horizontalLines: number[];
|
|
} {
|
|
const { columns, gap, padding } = gridSettings;
|
|
const gridInfo = calculateGridInfo(containerWidth, containerHeight, gridSettings);
|
|
const { columnWidth } = gridInfo;
|
|
|
|
// 세로 격자선 (컬럼 경계)
|
|
const verticalLines: number[] = [];
|
|
for (let i = 0; i <= columns; i++) {
|
|
const x = padding + i * (columnWidth + gap) - gap / 2;
|
|
if (x >= padding && x <= containerWidth - padding) {
|
|
verticalLines.push(x);
|
|
}
|
|
}
|
|
|
|
// 가로 격자선 (20px 단위)
|
|
const horizontalLines: number[] = [];
|
|
for (let y = padding; y < containerHeight; y += 20) {
|
|
horizontalLines.push(y);
|
|
}
|
|
|
|
return {
|
|
verticalLines,
|
|
horizontalLines,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 컴포넌트가 격자 경계에 있는지 확인
|
|
*/
|
|
export function isOnGridBoundary(
|
|
position: Position,
|
|
size: Size,
|
|
gridInfo: GridInfo,
|
|
gridSettings: GridSettings,
|
|
tolerance: number = 5,
|
|
): boolean {
|
|
const snappedPos = snapToGrid(position, gridInfo, gridSettings);
|
|
const snappedSize = snapSizeToGrid(size, gridInfo, gridSettings);
|
|
|
|
const positionMatch =
|
|
Math.abs(position.x - snappedPos.x) <= tolerance && Math.abs(position.y - snappedPos.y) <= tolerance;
|
|
|
|
const sizeMatch =
|
|
Math.abs(size.width - snappedSize.width) <= tolerance && Math.abs(size.height - snappedSize.height) <= tolerance;
|
|
|
|
return positionMatch && sizeMatch;
|
|
}
|