Корректная расстановка стенок #7

Merged
rust merged 15 commits from test into main 2026-01-11 15:58:40 +03:00
Showing only changes of commit 438c7b6615 - Show all commits
+48 -34
View File
@@ -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">
{(() => { {(() => {