Spaces:
Running
Running
Zhen Ye
Claude Opus 4.6
commited on
Commit
·
3015ea3
1
Parent(s):
7124ca1
feat: click-to-select tracks on video overlay
Browse filesAdd click handler on engage overlay canvas for selecting tracked objects
directly in the video. Hit-tests against track bboxes with smallest-area
priority for overlapping objects, dispatches track-selected event.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- frontend/js/core/video.js +1 -0
- frontend/js/main.js +4 -1
- frontend/js/ui/overlays.js +39 -0
frontend/js/core/video.js
CHANGED
|
@@ -212,6 +212,7 @@ APP.core.video.resizeOverlays = function () {
|
|
| 212 |
const h = videoEngage.videoHeight || state.frame.h;
|
| 213 |
engageOverlay.width = w;
|
| 214 |
engageOverlay.height = h;
|
|
|
|
| 215 |
}
|
| 216 |
|
| 217 |
if (frameOverlay) {
|
|
|
|
| 212 |
const h = videoEngage.videoHeight || state.frame.h;
|
| 213 |
engageOverlay.width = w;
|
| 214 |
engageOverlay.height = h;
|
| 215 |
+
engageOverlay.style.pointerEvents = "auto";
|
| 216 |
}
|
| 217 |
|
| 218 |
if (frameOverlay) {
|
frontend/js/main.js
CHANGED
|
@@ -12,7 +12,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 12 |
const { load: loadDemo, getFrameData: getDemoFrameData, enable: enableDemo } = APP.core.demo;
|
| 13 |
|
| 14 |
// UI Renderers
|
| 15 |
-
const { renderFrameOverlay, renderEngageOverlay } = APP.ui.overlays;
|
| 16 |
const { renderFrameTrackList } = APP.ui.cards;
|
| 17 |
const { tickAgentCursor, moveCursorToRect } = APP.ui.cursor;
|
| 18 |
const { matchAndUpdateTracks, predictTracks } = APP.core.tracker;
|
|
@@ -60,6 +60,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 60 |
syncKnobDisplays();
|
| 61 |
setHfStatus("idle");
|
| 62 |
|
|
|
|
|
|
|
|
|
|
| 63 |
// Start main loop
|
| 64 |
requestAnimationFrame(loop);
|
| 65 |
|
|
|
|
| 12 |
const { load: loadDemo, getFrameData: getDemoFrameData, enable: enableDemo } = APP.core.demo;
|
| 13 |
|
| 14 |
// UI Renderers
|
| 15 |
+
const { renderFrameOverlay, renderEngageOverlay, initClickHandler } = APP.ui.overlays;
|
| 16 |
const { renderFrameTrackList } = APP.ui.cards;
|
| 17 |
const { tickAgentCursor, moveCursorToRect } = APP.ui.cursor;
|
| 18 |
const { matchAndUpdateTracks, predictTracks } = APP.core.tracker;
|
|
|
|
| 60 |
syncKnobDisplays();
|
| 61 |
setHfStatus("idle");
|
| 62 |
|
| 63 |
+
// Enable click-to-select on engage overlay
|
| 64 |
+
initClickHandler();
|
| 65 |
+
|
| 66 |
// Start main loop
|
| 67 |
requestAnimationFrame(loop);
|
| 68 |
|
frontend/js/ui/overlays.js
CHANGED
|
@@ -39,6 +39,45 @@ APP.ui.overlays.renderFrameOverlay = function () {
|
|
| 39 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 40 |
};
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
APP.ui.overlays.renderEngageOverlay = function () {
|
| 43 |
const { state } = APP.core;
|
| 44 |
const { $ } = APP.core.utils;
|
|
|
|
| 39 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 40 |
};
|
| 41 |
|
| 42 |
+
APP.ui.overlays.initClickHandler = function () {
|
| 43 |
+
const { $ } = APP.core.utils;
|
| 44 |
+
const canvas = $("#engageOverlay");
|
| 45 |
+
if (!canvas) return;
|
| 46 |
+
|
| 47 |
+
canvas.style.pointerEvents = "auto";
|
| 48 |
+
canvas.style.cursor = "crosshair";
|
| 49 |
+
|
| 50 |
+
canvas.addEventListener("click", (e) => {
|
| 51 |
+
const { state } = APP.core;
|
| 52 |
+
const tracks = state.tracker.tracks;
|
| 53 |
+
if (!tracks || !tracks.length) return;
|
| 54 |
+
|
| 55 |
+
// Convert click to normalized 0-1 coords relative to canvas
|
| 56 |
+
const rect = canvas.getBoundingClientRect();
|
| 57 |
+
const nx = (e.clientX - rect.left) / rect.width;
|
| 58 |
+
const ny = (e.clientY - rect.top) / rect.height;
|
| 59 |
+
|
| 60 |
+
// Hit-test against track bboxes (smallest area wins for overlaps)
|
| 61 |
+
let best = null;
|
| 62 |
+
let bestArea = Infinity;
|
| 63 |
+
|
| 64 |
+
for (const t of tracks) {
|
| 65 |
+
const b = t.bbox;
|
| 66 |
+
if (!b) continue;
|
| 67 |
+
if (nx >= b.x && nx <= b.x + b.w && ny >= b.y && ny <= b.y + b.h) {
|
| 68 |
+
const area = b.w * b.h;
|
| 69 |
+
if (area < bestArea) {
|
| 70 |
+
bestArea = area;
|
| 71 |
+
best = t;
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
const id = best ? best.id : null;
|
| 77 |
+
document.dispatchEvent(new CustomEvent("track-selected", { detail: { id } }));
|
| 78 |
+
});
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
APP.ui.overlays.renderEngageOverlay = function () {
|
| 82 |
const { state } = APP.core;
|
| 83 |
const { $ } = APP.core.utils;
|