(node, canvasEvt, imgPos)
| 353 | } |
| 354 | |
| 355 | function showMenu(node, canvasEvt, imgPos) { |
| 356 | const ed = node._ed; |
| 357 | const menu = ed.menu; |
| 358 | menu.innerHTML = ""; |
| 359 | |
| 360 | const hit = hitTestPoint(node, mouseToCanvas(node, canvasEvt)); |
| 361 | |
| 362 | const items = []; |
| 363 | items.push({ |
| 364 | label: "Add Point at Cursor", |
| 365 | action: () => { |
| 366 | ed.splines[ed.active].push({ x: imgPos.x, y: imgPos.y }); |
| 367 | ed.dirty = true; |
| 368 | syncWidgets(node); |
| 369 | }, |
| 370 | }); |
| 371 | items.push({ |
| 372 | label: "Subdivide Nearest Segment", |
| 373 | action: () => { |
| 374 | const spline = ed.splines[ed.active]; |
| 375 | if (spline.length < 2) return; |
| 376 | const { segIndex } = closestSegment(spline, imgPos); |
| 377 | const a = spline[segIndex]; |
| 378 | const b = spline[segIndex + 1]; |
| 379 | const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }; |
| 380 | spline.splice(segIndex + 1, 0, mid); |
| 381 | ed.dirty = true; |
| 382 | syncWidgets(node); |
| 383 | }, |
| 384 | }); |
| 385 | items.push({ separator: true }); |
| 386 | items.push({ |
| 387 | label: "New Spline", |
| 388 | action: () => { |
| 389 | ed.splines.push([ |
| 390 | { x: imgPos.x - 40, y: imgPos.y }, |
| 391 | { x: imgPos.x + 40, y: imgPos.y }, |
| 392 | ]); |
| 393 | ed.active = ed.splines.length - 1; |
| 394 | ed.dirty = true; |
| 395 | syncWidgets(node); |
| 396 | }, |
| 397 | }); |
| 398 | items.push({ |
| 399 | label: "New Static Spline", |
| 400 | action: () => { |
| 401 | ed.splines.push([{ x: imgPos.x, y: imgPos.y }]); |
| 402 | ed.active = ed.splines.length - 1; |
| 403 | ed.dirty = true; |
| 404 | syncWidgets(node); |
| 405 | }, |
| 406 | }); |
| 407 | items.push({ |
| 408 | label: "Delete Spline", |
| 409 | action: () => { |
| 410 | if (ed.splines.length <= 1) return; |
| 411 | ed.splines.splice(ed.active, 1); |
| 412 | ed.active = Math.min(ed.active, ed.splines.length - 1); |
no test coverage detected