Spaces:
Running on Zero
Running on Zero
Relayout kinematic tree on responsive resize
Browse files
app.py
CHANGED
|
@@ -1330,9 +1330,10 @@ KINEMATIC_TREE_EDITOR_JS = r"""
|
|
| 1330 |
function defaultNodePosition(index) {
|
| 1331 |
const width = canvas.clientWidth || 320;
|
| 1332 |
const height = canvas.clientHeight || 380;
|
|
|
|
| 1333 |
const columns = width >= 680 ? 4 : width >= 500 ? 3 : width >= 340 ? 2 : 1;
|
| 1334 |
-
const usableX = Math.max(0, width -
|
| 1335 |
-
const usableY = Math.max(0, height -
|
| 1336 |
const columnGap = columns <= 1 ? 0 : usableX / Math.max(1, columns - 1);
|
| 1337 |
const rowCount = Math.max(1, Math.ceil((index + 1) / columns));
|
| 1338 |
const rowGap = rowCount <= 1 ? 0 : Math.min(118, usableY / Math.max(1, rowCount - 1));
|
|
@@ -1342,13 +1343,36 @@ KINEMATIC_TREE_EDITOR_JS = r"""
|
|
| 1342 |
};
|
| 1343 |
}
|
| 1344 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1345 |
function layoutTreePositions(rawLinks, joints) {
|
| 1346 |
const width = Math.max(canvas.clientWidth || 640, NODE_WIDTH + 72);
|
| 1347 |
const height = Math.max(canvas.clientHeight || 420, NODE_HEIGHT + 72);
|
| 1348 |
const marginX = 28;
|
| 1349 |
const marginY = 28;
|
| 1350 |
-
const
|
| 1351 |
-
const
|
|
|
|
| 1352 |
const linkCount = rawLinks.length;
|
| 1353 |
const childrenByParent = new Map();
|
| 1354 |
const childIds = new Set();
|
|
@@ -1419,6 +1443,45 @@ KINEMATIC_TREE_EDITOR_JS = r"""
|
|
| 1419 |
return positions;
|
| 1420 |
}
|
| 1421 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1422 |
function loadTree(tree, options = {}) {
|
| 1423 |
const links = Array.isArray(tree.links) ? tree.links : [];
|
| 1424 |
const joints = Array.isArray(tree.joints)
|
|
@@ -1448,6 +1511,10 @@ KINEMATIC_TREE_EDITOR_JS = r"""
|
|
| 1448 |
if (options.syncPrompts !== false) {
|
| 1449 |
syncPrompts();
|
| 1450 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1451 |
render();
|
| 1452 |
}
|
| 1453 |
|
|
@@ -1880,7 +1947,8 @@ KINEMATIC_TREE_EDITOR_JS = r"""
|
|
| 1880 |
}
|
| 1881 |
|
| 1882 |
function nodeCenter(link) {
|
| 1883 |
-
|
|
|
|
| 1884 |
}
|
| 1885 |
|
| 1886 |
function getLink(id) {
|
|
@@ -2082,8 +2150,9 @@ KINEMATIC_TREE_EDITOR_JS = r"""
|
|
| 2082 |
if (Math.abs(dx) + Math.abs(dy) > 3) {
|
| 2083 |
state.dragging.moved = true;
|
| 2084 |
}
|
| 2085 |
-
const
|
| 2086 |
-
const
|
|
|
|
| 2087 |
link.x = Math.min(maxX, Math.max(0, state.dragging.nodeX + dx));
|
| 2088 |
link.y = Math.min(maxY, Math.max(0, state.dragging.nodeY + dy));
|
| 2089 |
node.style.left = `${link.x}px`;
|
|
@@ -2343,10 +2412,11 @@ KINEMATIC_TREE_EDITOR_JS = r"""
|
|
| 2343 |
render();
|
| 2344 |
}
|
| 2345 |
});
|
| 2346 |
-
window.addEventListener("resize",
|
| 2347 |
-
|
| 2348 |
-
|
| 2349 |
-
|
|
|
|
| 2350 |
window.setInterval(() => {
|
| 2351 |
loadPromptMeshFromBox();
|
| 2352 |
loadExternalTreeFromBox();
|
|
|
|
| 1330 |
function defaultNodePosition(index) {
|
| 1331 |
const width = canvas.clientWidth || 320;
|
| 1332 |
const height = canvas.clientHeight || 380;
|
| 1333 |
+
const { width: nodeWidth, height: nodeHeight } = currentNodeSize();
|
| 1334 |
const columns = width >= 680 ? 4 : width >= 500 ? 3 : width >= 340 ? 2 : 1;
|
| 1335 |
+
const usableX = Math.max(0, width - nodeWidth - 48);
|
| 1336 |
+
const usableY = Math.max(0, height - nodeHeight - 48);
|
| 1337 |
const columnGap = columns <= 1 ? 0 : usableX / Math.max(1, columns - 1);
|
| 1338 |
const rowCount = Math.max(1, Math.ceil((index + 1) / columns));
|
| 1339 |
const rowGap = rowCount <= 1 ? 0 : Math.min(118, usableY / Math.max(1, rowCount - 1));
|
|
|
|
| 1343 |
};
|
| 1344 |
}
|
| 1345 |
|
| 1346 |
+
function currentNodeSize() {
|
| 1347 |
+
const node = nodeLayer.querySelector(".kin-node");
|
| 1348 |
+
if (!node) {
|
| 1349 |
+
return { width: NODE_WIDTH, height: NODE_HEIGHT };
|
| 1350 |
+
}
|
| 1351 |
+
const rect = node.getBoundingClientRect();
|
| 1352 |
+
return {
|
| 1353 |
+
width: Math.max(1, rect.width || NODE_WIDTH),
|
| 1354 |
+
height: Math.max(1, rect.height || NODE_HEIGHT)
|
| 1355 |
+
};
|
| 1356 |
+
}
|
| 1357 |
+
|
| 1358 |
+
function clampNodePositions() {
|
| 1359 |
+
const { width: nodeWidth, height: nodeHeight } = currentNodeSize();
|
| 1360 |
+
const maxX = Math.max(0, (canvas.clientWidth || 0) - nodeWidth - 10);
|
| 1361 |
+
const maxY = Math.max(0, (canvas.clientHeight || 0) - nodeHeight - 10);
|
| 1362 |
+
state.links.forEach((link) => {
|
| 1363 |
+
link.x = Math.min(maxX, Math.max(0, Number(link.x) || 0));
|
| 1364 |
+
link.y = Math.min(maxY, Math.max(0, Number(link.y) || 0));
|
| 1365 |
+
});
|
| 1366 |
+
}
|
| 1367 |
+
|
| 1368 |
function layoutTreePositions(rawLinks, joints) {
|
| 1369 |
const width = Math.max(canvas.clientWidth || 640, NODE_WIDTH + 72);
|
| 1370 |
const height = Math.max(canvas.clientHeight || 420, NODE_HEIGHT + 72);
|
| 1371 |
const marginX = 28;
|
| 1372 |
const marginY = 28;
|
| 1373 |
+
const { width: nodeWidth, height: nodeHeight } = currentNodeSize();
|
| 1374 |
+
const usableX = Math.max(0, width - nodeWidth - marginX * 2);
|
| 1375 |
+
const usableY = Math.max(0, height - nodeHeight - marginY * 2);
|
| 1376 |
const linkCount = rawLinks.length;
|
| 1377 |
const childrenByParent = new Map();
|
| 1378 |
const childIds = new Set();
|
|
|
|
| 1443 |
return positions;
|
| 1444 |
}
|
| 1445 |
|
| 1446 |
+
function relayoutForCurrentCanvas() {
|
| 1447 |
+
if (state.dragging) {
|
| 1448 |
+
return;
|
| 1449 |
+
}
|
| 1450 |
+
const tree = exportTree();
|
| 1451 |
+
const positions = layoutTreePositions(tree.links, state.joints);
|
| 1452 |
+
state.links.forEach((link, index) => {
|
| 1453 |
+
const position = positions[index] || defaultNodePosition(index);
|
| 1454 |
+
link.x = position.x;
|
| 1455 |
+
link.y = position.y;
|
| 1456 |
+
});
|
| 1457 |
+
clampNodePositions();
|
| 1458 |
+
render();
|
| 1459 |
+
}
|
| 1460 |
+
|
| 1461 |
+
let relayoutAnimationFrame = null;
|
| 1462 |
+
let lastCanvasSize = { width: 0, height: 0 };
|
| 1463 |
+
function scheduleResponsiveRelayout() {
|
| 1464 |
+
if (relayoutAnimationFrame !== null) {
|
| 1465 |
+
window.cancelAnimationFrame(relayoutAnimationFrame);
|
| 1466 |
+
}
|
| 1467 |
+
relayoutAnimationFrame = window.requestAnimationFrame(() => {
|
| 1468 |
+
relayoutAnimationFrame = null;
|
| 1469 |
+
const width = canvas.clientWidth || 0;
|
| 1470 |
+
const height = canvas.clientHeight || 0;
|
| 1471 |
+
if (width <= 0 || height <= 0) {
|
| 1472 |
+
return;
|
| 1473 |
+
}
|
| 1474 |
+
const changed =
|
| 1475 |
+
Math.abs(width - lastCanvasSize.width) > 2 ||
|
| 1476 |
+
Math.abs(height - lastCanvasSize.height) > 2;
|
| 1477 |
+
lastCanvasSize = { width, height };
|
| 1478 |
+
if (changed) {
|
| 1479 |
+
relayoutForCurrentCanvas();
|
| 1480 |
+
renderPromptMesh();
|
| 1481 |
+
}
|
| 1482 |
+
});
|
| 1483 |
+
}
|
| 1484 |
+
|
| 1485 |
function loadTree(tree, options = {}) {
|
| 1486 |
const links = Array.isArray(tree.links) ? tree.links : [];
|
| 1487 |
const joints = Array.isArray(tree.joints)
|
|
|
|
| 1511 |
if (options.syncPrompts !== false) {
|
| 1512 |
syncPrompts();
|
| 1513 |
}
|
| 1514 |
+
lastCanvasSize = {
|
| 1515 |
+
width: canvas.clientWidth || 0,
|
| 1516 |
+
height: canvas.clientHeight || 0
|
| 1517 |
+
};
|
| 1518 |
render();
|
| 1519 |
}
|
| 1520 |
|
|
|
|
| 1947 |
}
|
| 1948 |
|
| 1949 |
function nodeCenter(link) {
|
| 1950 |
+
const { width: nodeWidth, height: nodeHeight } = currentNodeSize();
|
| 1951 |
+
return { x: link.x + nodeWidth * 0.5, y: link.y + nodeHeight * 0.5 };
|
| 1952 |
}
|
| 1953 |
|
| 1954 |
function getLink(id) {
|
|
|
|
| 2150 |
if (Math.abs(dx) + Math.abs(dy) > 3) {
|
| 2151 |
state.dragging.moved = true;
|
| 2152 |
}
|
| 2153 |
+
const { width: nodeWidth, height: nodeHeight } = currentNodeSize();
|
| 2154 |
+
const maxX = Math.max(0, canvas.clientWidth - nodeWidth - 10);
|
| 2155 |
+
const maxY = Math.max(0, canvas.clientHeight - nodeHeight - 10);
|
| 2156 |
link.x = Math.min(maxX, Math.max(0, state.dragging.nodeX + dx));
|
| 2157 |
link.y = Math.min(maxY, Math.max(0, state.dragging.nodeY + dy));
|
| 2158 |
node.style.left = `${link.x}px`;
|
|
|
|
| 2412 |
render();
|
| 2413 |
}
|
| 2414 |
});
|
| 2415 |
+
window.addEventListener("resize", scheduleResponsiveRelayout);
|
| 2416 |
+
if (typeof ResizeObserver !== "undefined") {
|
| 2417 |
+
const resizeObserver = new ResizeObserver(scheduleResponsiveRelayout);
|
| 2418 |
+
resizeObserver.observe(canvas);
|
| 2419 |
+
}
|
| 2420 |
window.setInterval(() => {
|
| 2421 |
loadPromptMeshFromBox();
|
| 2422 |
loadExternalTreeFromBox();
|