rayli commited on
Commit
1420ebf
·
verified ·
1 Parent(s): 19103fa

Relayout kinematic tree on responsive resize

Browse files
Files changed (1) hide show
  1. app.py +81 -11
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 - NODE_WIDTH - 48);
1335
- const usableY = Math.max(0, height - NODE_HEIGHT - 48);
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 usableX = Math.max(0, width - NODE_WIDTH - marginX * 2);
1351
- const usableY = Math.max(0, height - NODE_HEIGHT - marginY * 2);
 
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
- return { x: link.x + NODE_WIDTH * 0.5, y: link.y + NODE_HEIGHT * 0.5 };
 
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 maxX = Math.max(0, canvas.clientWidth - NODE_WIDTH - 10);
2086
- const maxY = Math.max(0, canvas.clientHeight - NODE_HEIGHT - 10);
 
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
- render();
2348
- renderPromptMesh();
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();