Корректная расстановка стенок #7
@@ -67,13 +67,12 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
let maxLimit = 1;
|
let maxLimit = 1;
|
||||||
|
|
||||||
parts.forEach(p => {
|
parts.forEach(p => {
|
||||||
// Если стенка параллельна нашей, мы ищем, не является ли она барьером
|
// Если стенка параллельна нашей
|
||||||
if (p.axis === axis) {
|
if (p.axis === axis) {
|
||||||
const pMin = p.min ?? 0;
|
const pMin = p.min ?? 0;
|
||||||
const pMax = p.max ?? 1;
|
const pMax = p.max ?? 1;
|
||||||
|
|
||||||
// Проверяем, пересекаются ли они "в проекции"
|
// Проверяем пересечение проекций
|
||||||
// crossPos - это середина нашей стенки. Попадает ли она в диапазон соседки?
|
|
||||||
if (crossPos > pMin && crossPos < pMax) {
|
if (crossPos > pMin && crossPos < pMax) {
|
||||||
if (p.offset < currentOffset) minLimit = Math.max(minLimit, p.offset);
|
if (p.offset < currentOffset) minLimit = Math.max(minLimit, p.offset);
|
||||||
if (p.offset > currentOffset) maxLimit = Math.min(maxLimit, p.offset);
|
if (p.offset > currentOffset) maxLimit = Math.min(maxLimit, p.offset);
|
||||||
@@ -83,7 +82,7 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
return { min: minLimit, max: maxLimit };
|
return { min: minLimit, max: maxLimit };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Поиск границ для НОВОЙ стенки (чтобы обрезать её до T-соединения)
|
// Поиск границ для НОВОЙ стенки (T-соединения)
|
||||||
const getHoveredBoundaries = (lx: number, ly: number, parts: Partition[]) => {
|
const getHoveredBoundaries = (lx: number, ly: number, parts: Partition[]) => {
|
||||||
let minX = 0, maxX = 1;
|
let minX = 0, maxX = 1;
|
||||||
let minY = 0, maxY = 1;
|
let minY = 0, maxY = 1;
|
||||||
@@ -93,13 +92,13 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
const pMax = p.max ?? 1;
|
const pMax = p.max ?? 1;
|
||||||
|
|
||||||
if (p.axis === 'x') {
|
if (p.axis === 'x') {
|
||||||
// Вертикальная стенка. Блокирует горизонтальное движение?
|
// Вертикальная стенка. Ограничивает по X.
|
||||||
if (ly >= pMin && ly <= pMax) {
|
if (ly >= pMin && ly <= pMax) {
|
||||||
if (p.offset < lx) minX = Math.max(minX, p.offset);
|
if (p.offset < lx) minX = Math.max(minX, p.offset);
|
||||||
if (p.offset > lx) maxX = Math.min(maxX, p.offset);
|
if (p.offset > lx) maxX = Math.min(maxX, p.offset);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Горизонтальная стенка
|
// Горизонтальная стенка. Ограничивает по Y.
|
||||||
if (lx >= pMin && lx <= pMax) {
|
if (lx >= pMin && lx <= pMax) {
|
||||||
if (p.offset < ly) minY = Math.max(minY, p.offset);
|
if (p.offset < ly) minY = Math.max(minY, p.offset);
|
||||||
if (p.offset > ly) maxY = Math.min(maxY, p.offset);
|
if (p.offset > ly) maxY = Math.min(maxY, p.offset);
|
||||||
@@ -164,6 +163,7 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
newSplits[dragging.axis][dragging.index] = val;
|
newSplits[dragging.axis][dragging.index] = val;
|
||||||
onChange(newSplits);
|
onChange(newSplits);
|
||||||
} else {
|
} else {
|
||||||
|
// Dragging Partition
|
||||||
const [iStr, jStr] = dragging.cellKey.split('-');
|
const [iStr, jStr] = dragging.cellKey.split('-');
|
||||||
const i = parseInt(iStr); const j = parseInt(jStr);
|
const i = parseInt(iStr); const j = parseInt(jStr);
|
||||||
const cellX1 = sortedX[i]; const cellX2 = sortedX[i+1];
|
const cellX1 = sortedX[i]; const cellX2 = sortedX[i+1];
|
||||||
@@ -175,7 +175,11 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
} else {
|
} else {
|
||||||
newOffset = (ny - cellY1) / (cellY2 - cellY1);
|
newOffset = (ny - cellY1) / (cellY2 - cellY1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ограничиваем перетаскивание.
|
||||||
|
// В идеале нужно динамически считать соседей, но для скорости пока ограничим ячейкой (2%-98%)
|
||||||
newOffset = Math.max(0.02, Math.min(0.98, newOffset));
|
newOffset = Math.max(0.02, Math.min(0.98, newOffset));
|
||||||
|
|
||||||
if (!isNaN(newOffset)) {
|
if (!isNaN(newOffset)) {
|
||||||
updatePartition(dragging.cellKey, dragging.id, { offset: newOffset });
|
updatePartition(dragging.cellKey, dragging.id, { offset: newOffset });
|
||||||
}
|
}
|
||||||
@@ -240,18 +244,23 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
if (found) {
|
if (found) {
|
||||||
setHoveredPartition({ id: found.id, cellKey: key });
|
setHoveredPartition({ id: found.id, cellKey: key });
|
||||||
} else {
|
} else {
|
||||||
// Calculate Phantom Partition
|
// Calculate Phantom Partition with T-junctions
|
||||||
const bounds = getHoveredBoundaries(lx, ly, parts);
|
const bounds = getHoveredBoundaries(lx, ly, parts);
|
||||||
const distL = lx - bounds.minX; const distR = bounds.maxX - lx;
|
|
||||||
const distT = ly - bounds.minY; const distB = bounds.maxY - ly;
|
const distL = lx - bounds.minX;
|
||||||
|
const distR = bounds.maxX - lx;
|
||||||
|
const distT = ly - bounds.minY;
|
||||||
|
const distB = bounds.maxY - ly;
|
||||||
|
|
||||||
const minX = Math.min(distL, distR);
|
const minX = Math.min(distL, distR);
|
||||||
const minY = Math.min(distT, distB);
|
const minY = Math.min(distT, distB);
|
||||||
|
|
||||||
const axis = minX < minY ? 'y' : 'x';
|
const axis = minX < minY ? 'y' : 'x';
|
||||||
|
|
||||||
const width = bounds.maxX - bounds.minX;
|
const width = bounds.maxX - bounds.minX;
|
||||||
const height = bounds.maxY - bounds.minY;
|
const height = bounds.maxY - bounds.minY;
|
||||||
|
|
||||||
|
// Рисуем фантом только если есть место (>10% ячейки)
|
||||||
if ((axis === 'y' && height > 0.1) || (axis === 'x' && width > 0.1)) {
|
if ((axis === 'y' && height > 0.1) || (axis === 'x' && width > 0.1)) {
|
||||||
const offset = axis === 'x' ? lx : ly;
|
const offset = axis === 'x' ? lx : ly;
|
||||||
const min = axis === 'x' ? bounds.minY : bounds.minX;
|
const min = axis === 'x' ? bounds.minY : bounds.minX;
|
||||||
@@ -308,7 +317,6 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
const x1 = sortedX[i]; const x2 = sortedX[i + 1];
|
const x1 = sortedX[i]; const x2 = sortedX[i + 1];
|
||||||
const y1 = sortedY[j]; const y2 = sortedY[j + 1];
|
const y1 = sortedY[j]; const y2 = sortedY[j + 1];
|
||||||
|
|
||||||
// !!! FIX: Проверка y2
|
|
||||||
if (y2 === undefined || x2 === undefined) continue;
|
if (y2 === undefined || x2 === undefined) continue;
|
||||||
|
|
||||||
const cellX = x1 * viewBoxW; const cellY = y1 * viewBoxH;
|
const cellX = x1 * viewBoxW; const cellY = y1 * viewBoxH;
|
||||||
@@ -322,15 +330,16 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
const realW = (x2 - x1) * drawerW;
|
const realW = (x2 - x1) * drawerW;
|
||||||
const realD = (y2 - y1) * drawerD;
|
const realD = (y2 - y1) * drawerD;
|
||||||
|
|
||||||
// Label if empty
|
// --- LABEL FOR EMPTY CELL ---
|
||||||
if (parts.length === 0) {
|
if (parts.length === 0) {
|
||||||
const labelX = cellX + cellW / 2;
|
const labelX = cellX + cellW / 2;
|
||||||
const labelY = cellY + cellH / 2;
|
const labelY = cellY + cellH / 2;
|
||||||
const textW = Math.max(0, realW - wallThick).toFixed(0);
|
const textW = Math.max(0, realW - wallThick).toFixed(0);
|
||||||
const textD = Math.max(0, realD - wallThick).toFixed(0);
|
const textD = Math.max(0, realD - wallThick).toFixed(0);
|
||||||
|
// Увеличен шрифт до 16px
|
||||||
if (cellH > 40 && cellW > 60) {
|
if (cellH > 40 && cellW > 60) {
|
||||||
elements.push(
|
elements.push(
|
||||||
<text key={`label-${key}`} x={labelX} y={labelY} textAnchor="middle" dominantBaseline="middle" className="fill-slate-300 font-mono text-[14px] font-bold pointer-events-none select-none opacity-80" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.8)' }}>
|
<text key={`label-${key}`} x={labelX} y={labelY} textAnchor="middle" dominantBaseline="middle" className="fill-slate-300 font-mono text-[16px] font-bold pointer-events-none select-none opacity-80" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.8)' }}>
|
||||||
{textW} × {textD}
|
{textW} × {textD}
|
||||||
</text>
|
</text>
|
||||||
);
|
);
|
||||||
@@ -344,36 +353,34 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
|
|
||||||
{parts.map(p => {
|
{parts.map(p => {
|
||||||
const pMin = p.min ?? 0; const pMax = p.max ?? 1;
|
const pMin = p.min ?? 0; const pMax = p.max ?? 1;
|
||||||
let lx1, ly1, lx2, ly2;
|
let lx1, ly1, lx2, ly2; // Координаты линии
|
||||||
let tx, ty;
|
let dist1 = 0, dist2 = 0; // Расстояния
|
||||||
let dist1 = 0, dist2 = 0;
|
|
||||||
|
|
||||||
// Ищем границы для размеров
|
// Ищем соседей для расчета размеров
|
||||||
const neighbors = getNeighborOffsets(p.offset, (pMin + pMax)/2, p.axis, parts);
|
const neighbors = getNeighborOffsets(p.offset, (pMin + pMax)/2, p.axis, parts);
|
||||||
|
const isVertical = p.axis === 'x';
|
||||||
|
|
||||||
if (p.axis === 'x') {
|
if (isVertical) {
|
||||||
const px = cellX + (cellW * p.offset);
|
const px = cellX + (cellW * p.offset);
|
||||||
lx1 = px; lx2 = px;
|
lx1 = px; lx2 = px;
|
||||||
ly1 = cellY + (cellH * pMin); ly2 = cellY + (cellH * pMax);
|
ly1 = cellY + (cellH * pMin); ly2 = cellY + (cellH * pMax);
|
||||||
|
// Расчет расстояний по горизонтали
|
||||||
// Дистанция = (Мой Оффсет - Оффсет Соседа) * Ширину Ячейки - Толщина стенки
|
|
||||||
dist1 = Math.abs((p.offset - neighbors.min) * realW) - wallThick;
|
dist1 = Math.abs((p.offset - neighbors.min) * realW) - wallThick;
|
||||||
dist2 = Math.abs((neighbors.max - p.offset) * realW) - wallThick;
|
dist2 = Math.abs((neighbors.max - p.offset) * realW) - wallThick;
|
||||||
|
|
||||||
tx = px; ty = (ly1 + ly2) / 2;
|
|
||||||
} else {
|
} else {
|
||||||
const py = cellY + (cellH * p.offset);
|
const py = cellY + (cellH * p.offset);
|
||||||
ly1 = py; ly2 = py;
|
ly1 = py; ly2 = py;
|
||||||
lx1 = cellX + (cellW * pMin); lx2 = cellX + (cellW * pMax);
|
lx1 = cellX + (cellW * pMin); lx2 = cellX + (cellW * pMax);
|
||||||
|
// Расчет расстояний по вертикали
|
||||||
dist1 = Math.abs((p.offset - neighbors.min) * realD) - wallThick;
|
dist1 = Math.abs((p.offset - neighbors.min) * realD) - wallThick;
|
||||||
dist2 = Math.abs((neighbors.max - p.offset) * realD) - wallThick;
|
dist2 = Math.abs((neighbors.max - p.offset) * realD) - wallThick;
|
||||||
|
|
||||||
tx = (lx1 + lx2) / 2; ty = py;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSel = selectedPartitionId === p.id;
|
const isSel = selectedPartitionId === p.id;
|
||||||
const isHov = hoveredPartition?.id === p.id;
|
const isHov = hoveredPartition?.id === p.id;
|
||||||
|
const midX = (lx1 + lx2) / 2;
|
||||||
|
const midY = (ly1 + ly2) / 2;
|
||||||
|
const textOffset = 8; // Отступ текста от линии
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g key={p.id}>
|
<g key={p.id}>
|
||||||
@@ -382,24 +389,31 @@ export const LayoutStep: React.FC<Props> = ({ config, splits, onChange }) => {
|
|||||||
<line x1={lx1} y1={ly1} x2={lx2} y2={ly2} stroke={isSel ? "#a855f7" : (isHov ? "#d8b4fe" : "#7e22ce")} strokeWidth={isSel ? 6 : 4} strokeLinecap="round" />
|
<line x1={lx1} y1={ly1} x2={lx2} y2={ly2} stroke={isSel ? "#a855f7" : (isHov ? "#d8b4fe" : "#7e22ce")} strokeWidth={isSel ? 6 : 4} strokeLinecap="round" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{/* DIMENSIONS */}
|
{/* DIMENSIONS - Увеличен шрифт до 14px */}
|
||||||
<text x={tx} y={ty} className="pointer-events-none select-none font-mono text-[12px] font-bold fill-white" textAnchor="middle" dominantBaseline="middle" style={{ textShadow: '0px 0px 3px #000' }}>
|
<g className="pointer-events-none select-none font-mono text-[14px] font-bold fill-white" style={{ textShadow: '0px 0px 3px #000' }}>
|
||||||
{p.axis === 'x' ? (
|
{isVertical ? (
|
||||||
|
// Для вертикальных линий: два отдельных текста слева и справа, выровненных по центру высоты
|
||||||
<>
|
<>
|
||||||
<tspan dx="-18" fill="#e2e8f0">{Math.max(0, dist1).toFixed(0)}</tspan>
|
<text x={lx1 - textOffset} y={midY} textAnchor="end" dominantBaseline="middle">
|
||||||
<tspan dx="36" fill="#e2e8f0">{Math.max(0, dist2).toFixed(0)}</tspan>
|
{Math.max(0, dist1).toFixed(0)}
|
||||||
|
</text>
|
||||||
|
<text x={lx1 + textOffset} y={midY} textAnchor="start" dominantBaseline="middle">
|
||||||
|
{Math.max(0, dist2).toFixed(0)}
|
||||||
|
</text>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
// Для горизонтальных линий: один текст по центру с tspan сверху и снизу
|
||||||
<tspan x={tx} dy="-12" fill="#e2e8f0">{Math.max(0, dist1).toFixed(0)}</tspan>
|
<text x={midX} y={midY} textAnchor="middle" dominantBaseline="middle">
|
||||||
<tspan x={tx} dy="24" fill="#e2e8f0">{Math.max(0, dist2).toFixed(0)}</tspan>
|
<tspan x={midX} dy="-12">{Math.max(0, dist1).toFixed(0)}</tspan>
|
||||||
</>
|
<tspan x={midX} dy="24">{Math.max(0, dist2).toFixed(0)}</tspan>
|
||||||
|
</text>
|
||||||
)}
|
)}
|
||||||
</text>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* Phantom Line */}
|
||||||
{isHovered && phantomPartition && !hoveredPartition && !dragging && (
|
{isHovered && phantomPartition && !hoveredPartition && !dragging && (
|
||||||
<g className="pointer-events-none opacity-60">
|
<g className="pointer-events-none opacity-60">
|
||||||
{(() => {
|
{(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user